<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://brwiki2.brulescorp.com/brwiki2/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Gordon.dye</id>
	<title>BR Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://brwiki2.brulescorp.com/brwiki2/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Gordon.dye"/>
	<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Special:Contributions/Gordon.dye"/>
	<updated>2026-04-16T07:24:40Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.0</generator>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Screen_Operations&amp;diff=11506</id>
		<title>Screen Operations</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Screen_Operations&amp;diff=11506"/>
		<updated>2026-04-13T04:22:15Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Business Rules!]] advanced &#039;&#039;&#039;Screen Input/Output&#039;&#039;&#039; capabilities include many abilities:&lt;br /&gt;
* Print to and input from any position on the screen (full screen processing)&lt;br /&gt;
* Designation of one or more portions of the screen as its own mini-screen (windowing)&lt;br /&gt;
* Change the screen&#039;s display features through the use of screen attributes&lt;br /&gt;
&lt;br /&gt;
==Full Screen Processing==&lt;br /&gt;
&lt;br /&gt;
Full screen processing allows data entry [[controls]] on any position on the screen, specified by [[Row]] and [[Column]]. This contrasts with sequential screen processing, which allows prompted data display and entry with [[PRINT]], [[INPUT]], [[LINPUT]] and [[RINPUT]] statements, but only allows access to the bottom line of the screen. (Lines in regular screen processing then scroll up until they scroll off the top of the screen.) With full screen processing, any position on the screen may be addressed directly.&lt;br /&gt;
&lt;br /&gt;
;Besides allowing access to any position on the screen, full screen processing provides several additional advantages:&lt;br /&gt;
# Screens can be attractively designed, making programs appear professional.&lt;br /&gt;
# Less code is required. One screen that fully utilizes Windows control attributes and displays ten prompts and ten input fields can be programmed in as few as two statements.&lt;br /&gt;
# The operation of the input screen can be controlled. Control attributes may be used to specify starting cursor positions, automatic entry of data, field protection and more.&lt;br /&gt;
&lt;br /&gt;
===Graphical Console===&lt;br /&gt;
&lt;br /&gt;
A separate command console handles the operator commands without interfering with the graphical console. Print newpage goes to the command console if at the command prompt, and to both in a program.  [[Print]] anything else will only go to the [[command console]].&lt;br /&gt;
&lt;br /&gt;
The [[GUI console]] can be opened to other than 80 columns by 24 rows:&lt;br /&gt;
&lt;br /&gt;
An OPEN window statement may specify a parent window number.&lt;br /&gt;
&lt;br /&gt;
Two fonts are supported simultaneously, one for background and captions and one font for input fields.&lt;br /&gt;
&lt;br /&gt;
;Always [[Print Newpage]] Initially When Full Screen Processing&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Multiple Field Processing====&lt;br /&gt;
&lt;br /&gt;
The full screen processing statements allow you to input and output multiple fields with a single statement. This can be accomplished in two main ways: with repetitions of individual parameters directly within the full screen processing statement; or with the use of array parameters which reference a string variable, coded elsewhere in the program, that includes all the information required for the various specifications.&lt;br /&gt;
&lt;br /&gt;
In the following example, six fields are processed by each of two full screen-processing statements. In each case, all information required to process the multiple fields is included in the actual statement. In line 140, the field definitions and variable names are specified for six prompts. In line 150, the current values are output and the changed values (as updated by the operator) are input; this line also includes field help information for two of the six fields.&lt;br /&gt;
&lt;br /&gt;
 00120 DIM COMP$*25, STREET$*35, ADDR$*35, NAME$*20&lt;br /&gt;
 00130 PRINT NEWPAGE&lt;br /&gt;
 00140 PRINT FIELDS &amp;quot;6,11,c 20; 8,11,c 20; 10,11,c 20!:12,11,c 20; 14,11,c 20; 16,11,c 20&amp;quot; : PRID$, PRCOMP$,PRSTREET$, PRADDR$, PRNAME$, PRPHONE$&lt;br /&gt;
 00150 INPUT FIELDS &amp;quot;6,32,c 4,ra; 8,32,c 25,ra; 10,32,c 35,ra;12,32,c 35,ra; 14,32,c 20,ra; 16,32,c 12,ra&amp;quot;,attr &amp;quot;R&amp;quot;,help&amp;quot;XX2A&amp;lt;;Type in the street address.\nIf there is an apartment or suite number, \n type # and the number;2A&amp;lt;;Type in the full city name. \n Use the two-letter state code\n and the 9-digit zip code;&amp;quot;: ID$, COMP$, STREET$, ADDR$, NAME$, PHONE$&lt;br /&gt;
&lt;br /&gt;
One alternate way to code the above would be to place the field specifications and the field help window specifications each into their own array. The following code utilizes this technique but it operates exactly the same as the previous example. Line 220 prints the six prompts. Line 230 outputs the current values, accepts the updated values of the specified variables, and provides field help information on two of the six fields.&lt;br /&gt;
&lt;br /&gt;
 00110 DIM PROMPT$(6)*20, FLDDEF$(6)*16, DATADEF$(6)*15,FIELDHELP$*200&lt;br /&gt;
 00120 DIM COMP$*25, STREET$*35, ADDR$*35, NAME$*20&lt;br /&gt;
 00130 ! **** Operator Prompts For Prompt$ Array&lt;br /&gt;
 00140 DATA ID number,Company Name,Street address,City/State/ Zip, Contact, Phone&lt;br /&gt;
 00150 ! **** Field Definitions For Prompts (FLDDEF$)&lt;br /&gt;
 00160 DATA &amp;quot;6,11,c 20&amp;quot;,&amp;quot;8,11,c 20&amp;quot;,&amp;quot;10,11,c 20&amp;quot;,&amp;quot;12,11,c 20&amp;quot;,&amp;quot;14,11,c 20&amp;quot;,&amp;quot;16,11,c 20&amp;quot;&lt;br /&gt;
 00170 ! **** Field Definitions For Data Entry (DATADEF$)&lt;br /&gt;
 00180 DATA &amp;quot;6,32,c 4,ra&amp;quot;,&amp;quot;8,32,c 25,ra&amp;quot;,&amp;quot;10,32,c 35,ra&amp;quot;,&amp;quot;12,32,c 35,ra&amp;quot;,&amp;quot;14,32,c 20,ra&amp;quot;,&amp;quot;16,32,c 12,ra&amp;quot;&lt;br /&gt;
 00190 READ MAT PROMPT$, MAT FLDDEF$, MAT DATADEF$&lt;br /&gt;
 00200 LET FIELDHELP$=&amp;quot;XX2A&amp;lt;;Type in the street address.\nIf there is an apartment or suite number,\ntype # and the number;2A&amp;lt;; Type in the full city name.\nUse the two-letter state code \n and the 9-digit zip code;&amp;quot;&lt;br /&gt;
 00210 PRINT NEWPAGE&lt;br /&gt;
 00220 PRINT FIELDS MAT FLDDEF$: MAT PROMPT$&lt;br /&gt;
 00230 RINPUT FIELDS MAT DATADEF$,ATTR &amp;quot;R&amp;quot;, HELP FIELDHELP$: ID$, COMP$, STREET$, ADDR$, NAME$, PHONE$&lt;br /&gt;
&lt;br /&gt;
There are a few important items to be aware of when specifying multiple fields in full screen processing statements.&lt;br /&gt;
# When a string array is specified for field definitions (as with FLDDEF$ in line 220 above), extra elements in the string array will be ignored. Thus, if FLDDEF$ contained seven field definitions instead of six, the seventh would be ignored.&lt;br /&gt;
# The order of definitions in an array determines the order of processing. The first element of the array corresponds to the first item to be processed, the second element of the array corresponds to the second item to be processed, and so on. The order of items in the field definition does not affect your accessibility to the screen; you may define fields from the bottom of the screen to the top or from the top to the bottom or in any other way you prefer.&lt;br /&gt;
# It is important to remember that fields should be referenced in a consistent order throughout the full screen processing statement. The order that fields are referenced in the field definition array should be the same order that they are referenced with field help and variable names specifications.&lt;br /&gt;
# String and numeric fields may be intermixed in any order as long as the field definitions match the corresponding variable type. For instance, in the following example, the first field definition identifies a string field that starts at row 3, column 5 and is 20 characters long. This field definition corresponds to the first variable (NAME$) in the I/O list. Likewise, the second field definition identifies a three-digit number which corresponds to the numeric AGE variable.&lt;br /&gt;
&lt;br /&gt;
 1000 INPUT FIELDS &amp;quot;3,5,C 20; 7,5,N 3&amp;quot; : NAME$, AGE&lt;br /&gt;
&lt;br /&gt;
==Windowing==&lt;br /&gt;
&lt;br /&gt;
One of Business Rules most powerful screen I/O capabilities is the ability to separate the screen into several mini screens, or I/O windows. These windows can do all the same things (including scroll) that the regular full screen can do. In addition, an I/O window may have a border and a caption in the top line of the border.&lt;br /&gt;
&lt;br /&gt;
===Open Display (Screen)===&lt;br /&gt;
&lt;br /&gt;
 00410 OPEN #0: &amp;quot;Srow=nn, Scol=nn, Caption=APPLICATION NAME&amp;quot;, DISPLAY, OUTPUT&lt;br /&gt;
                -or-&lt;br /&gt;
 00420 OPEN #0:&amp;quot;Srow=nn, Scol=nn, Caption=APPLICATION NAME, Buttonrows=nn&amp;quot;,DISPLAY, OUTPUT&lt;br /&gt;
&lt;br /&gt;
00420 Open #0: opens the main window,  A number other than Zero opens a child window if &amp;quot;PARENT=XX&amp;quot; is included in the open string and XX refers to an existing open window number.&lt;br /&gt;
&lt;br /&gt;
Opens a Windows window.  Optionally sets the number of button rows (0-3). (Button rows are additional rows at the bottom of the screen that Business Rules can use for optional button placement.)&lt;br /&gt;
&lt;br /&gt;
Display file OPEN file reference strings can contain COPIES=nn.  The nn value will replace the new [COPIES] argument in SPOOLCMD.&lt;br /&gt;
&lt;br /&gt;
===Windows vs Field Help Windows===&lt;br /&gt;
&lt;br /&gt;
Although they share many similarities, Business Rules I/O windows and field help windows are specified in very different ways and they are used for very different purposes.&lt;br /&gt;
&lt;br /&gt;
The primary difference is that Business Rules treats an I/O window as its own input/output file. Information may be sent to and received from it with statements such as PRINT, INPUT, LINPUT, and RINPUT.&lt;br /&gt;
&lt;br /&gt;
In addition, information may be sent to and received from a window (as if it were the regular screen) by the full screen processing statements.&lt;br /&gt;
&lt;br /&gt;
In contrast, input from and output to field help windows are not allowed. Field help windows and the text, which appears within them, are specified by the full screen processing statements; they are designed to help the end-user determine what to do for a particular field.&lt;br /&gt;
&lt;br /&gt;
The following chart should help to clarify other similarities and differences between I/O windows and field help windows.&lt;br /&gt;
&lt;br /&gt;
[[Image:NEWC0050.jpg]]&lt;br /&gt;
&lt;br /&gt;
===Border===&lt;br /&gt;
Requires [[4.16]]+&lt;br /&gt;
&lt;br /&gt;
WINDOW BORDERS&lt;br /&gt;
&lt;br /&gt;
BORDER= any valid border spec produces a single line border.&lt;br /&gt;
&lt;br /&gt;
BORDER= NONE produces no border, same as not specifying a BORDER parameter at all.&lt;br /&gt;
&lt;br /&gt;
===Borders (Legacy)===&lt;br /&gt;
Only supported in Business Rules! versions prior to 4&lt;br /&gt;
&lt;br /&gt;
One of the greatest similarities between I/O windows and field help windows is in the types of borders that are available for each. For I/O windows, borders are specified with the [[OPEN window]] and [[PRINT BORDER]] statements. For field help windows, they are specified with the [[FieldHelp]] specification.&lt;br /&gt;
&lt;br /&gt;
Each of these three specifications uses syntax, which is nearly identical, where the border type is concerned. Each has a &amp;quot;BORDER&amp;quot; keyword (in the case of OPEN window, its &amp;quot;BORDER=&amp;quot;) followed by one of six options for specifying the border: &amp;quot;B&amp;quot; (blank), &amp;quot;D&amp;quot; (double-line), &amp;quot;H&amp;quot; (sHadow), &amp;quot;S&amp;quot; (single-line), &amp;quot;corners&amp;quot;, and &amp;quot;8 chars&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
 The first four border options are predesigned. The following illustrations show examples of each. (Keep in mind that the attributes specified with a border provide hundreds of opportunities for variation.)&lt;br /&gt;
&lt;br /&gt;
[[Image:HELP0134.jpg]]&lt;br /&gt;
&lt;br /&gt;
The last two border options allow you to custom-design your own borders. With the &amp;quot;corners&amp;quot; parameter, you can specify just the top left and bottom right characters (and let Business Rules fill in the rest). Or with the &amp;quot;8 chars&amp;quot; parameter you can specify eight characters that identify the corner and middle characters for every side of the border. The following illustration shows the sequence in which the eight characters must be specified:&lt;br /&gt;
&lt;br /&gt;
1|2|3&lt;br /&gt;
&lt;br /&gt;
8||4&lt;br /&gt;
&lt;br /&gt;
7|6|5&lt;br /&gt;
&lt;br /&gt;
The following example shows the use of the &amp;quot;8 chars&amp;quot; parameter with graphic drawing characters which may be toggled on for the 0-9 and period (.) keys by pressing Ctrl-\. This example creates the same border that is created by the S border type. It also includes the &amp;quot;r&amp;quot; attribute to reverse the border.&lt;br /&gt;
&lt;br /&gt;
[[Image:HELP0135.jpg]]&lt;br /&gt;
&lt;br /&gt;
The next sample code shows some of the options that are available with borders for windows. A good way to see both windows and field help windows in action would be to type this code in and use the RUN STEP command to execute it.&lt;br /&gt;
&lt;br /&gt;
 00005 PRINT NEWPAGE&lt;br /&gt;
 00007 DIM FIELDHELP$*50, NULL$*50&lt;br /&gt;
 00009 EXECUTE &amp;quot;config fieldhelp border=H&amp;quot;&lt;br /&gt;
 00010 OPEN #1:&amp;quot;srow=4,scol=10,erow=8,ecol=37,border=S,caption =&amp;gt;S (Single-line border)&amp;quot;,DISPLAY,OUTIN&lt;br /&gt;
 00020 OPEN #2: &amp;quot;srow=4,scol=45,erow=8,ecol=72,border=d&amp;quot;,DISPLAY,OUTIN&lt;br /&gt;
 00030 PRINT #2, FIELDS &amp;quot;3,4,cc 22&amp;quot;: &amp;quot;D (Double-line border)&amp;quot;&lt;br /&gt;
 00040 input fields &amp;quot;12,15,c 50,r&amp;quot;,help &amp;quot;1B; This is a field help window \n using an H (sHaded) border. \n \n Type in any CONFIG FIELDHELP \n specification such as the following: \n CONFIG FIELDHELP BORDER=BR;&amp;quot;: FIELDHELP$&lt;br /&gt;
 00045 PRINT FIELDS &amp;quot;12,15,C 50,N&amp;quot;: NULL$&lt;br /&gt;
 00050 EXECUTE FIELDHELP$&lt;br /&gt;
 00090 INPUT FIELDS &amp;quot;18,35,c 18&amp;quot;,help &amp;quot;1A; This window uses the border you specified. \n If you typed in the suggested border, \n it is B (blank) using the R (reverse) attribute.\n \n Now watch the upper left window while pressing&amp;lt;CR&amp;gt; \n to see the effects of PRINT BORDER.;&amp;quot;: CR$&lt;br /&gt;
 00100 PRINT #1, BORDER &amp;quot;*-*|*-*|&amp;quot;: &amp;quot;custom-designed border&amp;quot;&lt;br /&gt;
 00105 CLOSE #1,DROP:&lt;br /&gt;
 00106 CLOSE #2,DROP:&lt;br /&gt;
&lt;br /&gt;
In the above example, line 9 uses an [[EXECUTE]] statement to set the border type for all field help windows. Line 10 opens an I/O window with a single-line border and a caption in the top border that is right justified and says &amp;quot;S (Single-line border)&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Line 20 opens an I/O window with a double-line border. Line 30 then centers the string &amp;quot;D (Double-line border)&amp;quot; within the window. NOTE that the row and column specified in this statement assume that the top left corner of the window is row 1, column 1.&lt;br /&gt;
&lt;br /&gt;
Line 40 specifies a field that accepts input on line 12 of the regular screen. It includes a field help window with text that describes its border as a shaded border (this border type for field help windows was specified back in line 9). This window also tells the user to type in a CONFIG FIELDHELP specification that identifies a new border type for field help windows.&lt;br /&gt;
&lt;br /&gt;
Line 45 prints a null string over the area where the reversed input field for line 40 appeared, thus cleaning up the screen.&lt;br /&gt;
&lt;br /&gt;
Line 50 executes the string typed in for line 40 to change the border type for all field help windows.&lt;br /&gt;
&lt;br /&gt;
Line 90 specifies an input field that accepts input on line 18 of the regular screen. It includes a field help window that utilizes the new field help border type executed in line 50. It then instructs the user to watch the upper left window to see the effects of a PRINT BORDER statement.&lt;br /&gt;
&lt;br /&gt;
Line 100 uses PRINT BORDER to change the border in window #1. The specified border uses the &amp;quot;8 chars&amp;quot; parameter, which is available in all the Business Rules instructions that specify border types (OPEN window, PRINT BORDER, and FIELDHELP).&lt;br /&gt;
&lt;br /&gt;
Lines 105 and 106 close and drop the contents of the two open windows. Although the terminology may seem a little confusing at first, the act of closing a window causes the window&#039;s border to disappear and the information that was hidden by the appearance of the window to be restored. Thus, if lines 105 and 106 simply closed (without dropping) windows #1 and #2, the borders would disappear and a blank screen would be restored.&lt;br /&gt;
&lt;br /&gt;
However, the act of closing and dropping a window file leaves the current contents of the window and its border intact at the same time that it drops the information that was hidden by the appearance of the window.&lt;br /&gt;
&lt;br /&gt;
===Using I/O Windows to Save the Screen (Prior to 4.0)===&lt;br /&gt;
&lt;br /&gt;
Beginning with 4.17 the [[TABS]] feature is used to display multiple windows and switch between them.  See TABS later in this chapter.  Saving a portion of the screen is not necessary after 4.0 because the graphic window that is opened on top of an existing window does not destroy the information on the layer below.  The underlying information automatically becomes visible when the top window is closed.  In versions prior to 4.0 there were not separate layers, consequently the replaced information would need to be saved in a separate screen file and restored (closed) when the overlaying information was no longer needed.&lt;br /&gt;
&lt;br /&gt;
Saving all or part of the screen is very easy with I/O windows. All it requires is opening a borderless window using row and column parameters that encompass all the information to be saved. The following example saves an entire screen without making any changes to the appearance of the screen:&lt;br /&gt;
&lt;br /&gt;
 00670 OPEN #1:&amp;quot;SROW=1,SCOL=1,EROW=24,ECOL=80:,DISPLAY,OUTIN&lt;br /&gt;
&lt;br /&gt;
It is useful to note that an OPEN window statement&#039;s row and column parameters identify the inside portion of a window. If a border is specified for a window, it is always placed one position outside the specified row and column.&lt;br /&gt;
&lt;br /&gt;
Once an entire screen has been saved with a window, the existing screen may be altered any number of times without affecting what was saved from behind the window. Restoring the saved screen (including all attributes) is accomplished with a CLOSE statement as follows:&lt;br /&gt;
&lt;br /&gt;
 00750 CLOSE #1:&lt;br /&gt;
&lt;br /&gt;
The capability of saving a screen becomes very useful when you wish to switch between overlapping windows. The following example demonstrates such a use:&lt;br /&gt;
&lt;br /&gt;
 00010 print NEWPAGE&lt;br /&gt;
 00020 open #1: &amp;quot;srow=5,scol=5,erow=15,ecol=50,border=dn&amp;quot;,display,outin&lt;br /&gt;
 00030 print #1,fields &amp;quot;1,2,c 30&amp;quot;: &amp;quot;Business Rules window 1&amp;quot;&lt;br /&gt;
 00040 print #1,fields &amp;quot;10,2,c 30&amp;quot;: &amp;quot;F1 to go to window 2&amp;quot;&lt;br /&gt;
 00050 input #1,fields &amp;quot;4,4,c 10,u&amp;quot;: X$&lt;br /&gt;
 00060 if CMDKEY&amp;lt;&amp;gt;1 then goto 180&lt;br /&gt;
 00070 open #101: &amp;quot;srow=4,scol=4,erow=16,ecol=51&amp;quot;,display,outin&lt;br /&gt;
 00080 if FILE(2)&amp;lt;&amp;gt;-1 then goto 130&lt;br /&gt;
 00090 open #2: &amp;quot;srow=8,scol=20,erow=18,ecol=61,border=dn&amp;quot;,display,outin &lt;br /&gt;
 00100 print #2: NEWPAGE&lt;br /&gt;
 00110 print #2,fields &amp;quot;1,2,c 30&amp;quot;: &amp;quot;Business Rules window 2&amp;quot;&lt;br /&gt;
 00111 print #2,fields &amp;quot;9,2,c 30&amp;quot;: &amp;quot;F1 to go to window 1&amp;quot;&lt;br /&gt;
 00120 goto 140&lt;br /&gt;
 00130 close #102:&lt;br /&gt;
 00140 input #2,fields &amp;quot;4,4,c 10,u&amp;quot;: X$&lt;br /&gt;
 00150 if CMDKEY&amp;lt;&amp;gt;1 then goto 180&lt;br /&gt;
 00160 open #102:&amp;quot;srow=7,scol=19,erow=19,ecol=62&amp;quot;,display,outin&lt;br /&gt;
 00170 close #101: : goto 50&lt;br /&gt;
 00180 stop&lt;br /&gt;
&lt;br /&gt;
Essentially, the above program allows the user to switch between two windows by pressing the F1 key (lines 50 and 140). If the user presses F1 from window #1, the program saves window #1 by opening a borderless window (#101) that encompasses the data area of the window. It then opens window #2 (if window #2 already exists, window #102 is restored). If the user then presses F1 to go back to window #1, the contents of window #2 are saved in window #102. Window #101 is then restored (over the top of window #2) with the CLOSE statement. The same sequence of saving and restoring windows can continue indefinitely.&lt;br /&gt;
&lt;br /&gt;
The above sample works well when running on [[CONFIG GUI OFF]] mode.  This was the only available mode in version prior to 4.0.  With the introduction of []CONFIG GUI ON]], &amp;quot;Using I/O Windows to Save the Screen&amp;quot;, is no longer appropriate.  &lt;br /&gt;
&lt;br /&gt;
In [[CONFIG GUI OFF]] mode, opening a new window will preserve any information displayed at the time the window was created.  Essentially, the information &amp;quot;behind the window&amp;quot;, will &amp;quot;bleed through&amp;quot; the newly opened window.   You may change any information on the screen that has been protected by this window, and when you close the window, the original text will be restored.&lt;br /&gt;
&lt;br /&gt;
In the above sample, there is the illusion of multiple layers, but in reality, each window is only preserving a &amp;quot;screen shot&amp;quot; of the information, and not maintaining each of the layers.   In other words, your 80 x 24 visible screen looks correct, but no other information is displayed.  Information may be printed to any of the virtual layers, and it will be visible to the user, and when you close the window, the original text will be displayed as originally captured.&lt;br /&gt;
&lt;br /&gt;
As an example, you can update a window that covers part of the screen, and use Print #0, to update the &amp;quot;Visible Area&amp;quot; in front of the window.   Later when you close the window, the original information will be restored.&lt;br /&gt;
&lt;br /&gt;
Following the above example application, in GUI OFF mode:&lt;br /&gt;
&lt;br /&gt;
Line 10 erases the screen&lt;br /&gt;
Lines 20-50 Open Window #1, (5,5 to 15,50) and display some information.&lt;br /&gt;
&lt;br /&gt;
When the user presses &amp;quot;F1&amp;quot;:&lt;br /&gt;
Line 70 &amp;quot;Protects Window #1, (4,4 to 16,51), by opening window #101.  Notice this new window is large enough to cover the line draw.&lt;br /&gt;
* Note: Window #1 is still open, and could be used to display information, but regardless of how the screen is updated, the entire original information will be restored when window #101 is closed.&lt;br /&gt;
&lt;br /&gt;
Line 90-120 Open Window #2, (8,20 to 18,61) and display some more information&lt;br /&gt;
* Note: Window #2 covers up some of both windows #1 &amp;amp; #101.  Again, Window #2 protects the contents of these two windows.   And regardless of how the screen is updated (Handles #1,#101 or #2), the original information protected by window #2 will be restored when Window #2 is closed.&lt;br /&gt;
&lt;br /&gt;
Again the users presses &amp;quot;F1&amp;quot;:&lt;br /&gt;
Line 160 &amp;quot;Protects Window #2, (7,19 to 19,62), by opening window #102, Notice this new window is large enough to cover the line draw.&lt;br /&gt;
* Note: Windows #1,#101 and #2 are still open, and could be used to display information, but regardless of how the screen is updated, the entire original information will be restored when window #102 is closed.&lt;br /&gt;
&lt;br /&gt;
The next line performs some &amp;quot;Fancy Magic&amp;quot;.&lt;br /&gt;
Line 170 closes Window #101,  (4,4 to 16,51)   This causes the original text protected by Window #101 to be &amp;quot;restored&amp;quot;.  The effect of closing this window is that the original text created by Window #1, and then protected by Window #101, is now restored.&lt;br /&gt;
&lt;br /&gt;
As you continue pressing F1, windows #101 &amp;amp; #102 take turns protecting and the restoring the two windows.  The effect of this programing is the user sees what appears to be two complete layers.   In other words, there is the illusion that the contents of both windows are actually still on the screen, and that pressing &amp;quot;F1&amp;quot; is switching layers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In [[CONFIG GUI ON]] mode, opening a new window actually create a new layer or container without affecting the information behind it.  The new window is now transparent, so the effect is very similar to a [[Print Newpage]] command.   This new window may or may not have a border.  In this mode, you no longer have a single 80 x 24 layer, rather each window has it&#039;s own private space.   Information must be printed to the correct layer.   &lt;br /&gt;
&lt;br /&gt;
As an example, you can open a window that covers part of the screen, and use Print #0, to update the &amp;quot;Hidden Area&amp;quot; behind the window.   Later when you close the window, the updated information will be displayed.&lt;br /&gt;
&lt;br /&gt;
Examining the same code again, but using the GUI ON mode:&lt;br /&gt;
&lt;br /&gt;
Line 10 erases the screen&lt;br /&gt;
Lines 20-50 Open Window #1, (5,5 to 15,50) and display some information.&lt;br /&gt;
&lt;br /&gt;
When the user presses &amp;quot;F1&amp;quot;:&lt;br /&gt;
Line 70 &amp;quot;Completely covers Window #1, (4,4 to 16,51), by opening window #101.  &lt;br /&gt;
* Note: Window #1 is still open, and intact, but is no longer visible because window #101 is actually a new layer and contains no information   Windows are not transparent in GUI ON mode.  Window #1, remains unaffected as long as nothing is printed directly to Window #1.   You could if desired, update Window #1 directly, but the changes would not be immediately apparent.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Line 90-120 Open Window #2, (8,20 to 18,61) and display some more information&lt;br /&gt;
* Note: Window #2 creates a new layer that covers up some of both windows #1 &amp;amp; #101.  Because Window #101 is hiding Window #1, Window #2, appears to &amp;quot;Pop-Up&amp;quot;, with Window #1 &amp;quot;Disappearing&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Again the users presses &amp;quot;F1&amp;quot;:&lt;br /&gt;
Line 160 &amp;quot;Completely covers Window #2, (7,19 to 19,62), by opening window #102. Now the screen appears to be blank.&lt;br /&gt;
* Note: At this point, Window #1, is covered by Window #101, and Window #102 partially covers Window #1 &amp;amp; #101.   Window #102 is the &amp;quot;Top Layer&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Line 170 closes Window #101,  (4,4 to 16,51).  This causes a rather strange effect as compared to the GUI OFF version.  Window #1, is now partially visible because it is not covered by Window #101.   However, Windows #2 &amp;amp; #102 are still partially covering Window #1.   The effect is that Window #1 appears &amp;quot;Broken&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As you continue pressing F1, windows #101 &amp;amp; #102 take turns partially hiding the two windows.  In GUI ON mode, this sample application looks terrible!&lt;br /&gt;
&lt;br /&gt;
Make the following few changes to the sample program.&lt;br /&gt;
&lt;br /&gt;
 00070 ! OPEN #101: &amp;quot;srow=4,scol=4,erow=16,ecol=51&amp;quot;,DISPLAY,OUTIN&lt;br /&gt;
 00130 ! CLOSE #102:&lt;br /&gt;
 00160 ! OPEN #102: &amp;quot;srow=7,scol=19,erow=19,ecol=62&amp;quot;,DISPLAY,OUTIN&lt;br /&gt;
 00170 ! CLOSE #101: !:&lt;br /&gt;
       GOTO 50&lt;br /&gt;
&lt;br /&gt;
These small changes create a new behavior that is more appealing.  Now, pressing &amp;quot;F1&amp;quot; will simply move focus between the two windows.   Notice that Window #2, always covers Window #1, but the user can still enter data as the focus changes.&lt;br /&gt;
&lt;br /&gt;
A few more changes create the following example:&lt;br /&gt;
&lt;br /&gt;
 00010 PRINT NEWPAGE&lt;br /&gt;
 00020 OPEN #1: &amp;quot;srow=5,scol=5,erow=15,ecol=50,Tab=Window 1&amp;quot;,DISPLAY,OUTIN &lt;br /&gt;
 00030 PRINT #1,FIELDS &amp;quot;1,2,c 30&amp;quot;: &amp;quot;Business Rules window 1&amp;quot;&lt;br /&gt;
 00040 PRINT #1,FIELDS &amp;quot;10,2,c 30&amp;quot;: &amp;quot;F1 to go to window 2&amp;quot;&lt;br /&gt;
 00050 INPUT #1,FIELDS &amp;quot;4,4,c 10,u&amp;quot;: X$&lt;br /&gt;
 00060 IF FKEY=93 OR FKEY=99 THEN GOTO 180&lt;br /&gt;
 00080 IF FILE(2)&amp;lt;&amp;gt;-1 THEN GOTO 140&lt;br /&gt;
 00090 OPEN #2: &amp;quot;srow=5,scol=5,erow=15,ecol=50,TAB=Window 2&amp;quot;,DISPLAY,OUTIN &lt;br /&gt;
 00100 PRINT #2: NEWPAGE&lt;br /&gt;
 00110 PRINT #2,FIELDS &amp;quot;1,2,c 30&amp;quot;: &amp;quot;Business Rules window 2&amp;quot;&lt;br /&gt;
 00111 PRINT #2,FIELDS &amp;quot;9,2,c 30&amp;quot;: &amp;quot;F1 to go to window 1&amp;quot;&lt;br /&gt;
 00120 GOTO 140&lt;br /&gt;
 00140 INPUT #2,FIELDS &amp;quot;4,4,c 10,u&amp;quot;: X$&lt;br /&gt;
 00150 IF FKEY=93 OR FKEY=99 THEN GOTO 180&lt;br /&gt;
 00170 GOTO 50&lt;br /&gt;
 00180 STOP &lt;br /&gt;
&lt;br /&gt;
Run the program, and press &amp;quot;F1&amp;quot; as instructed.  Notice that the tabs in fact are separate layers that preserve the text you type.  As long as you are in GUI Mode, feel free to use the mouse to navigate the tabs.&lt;br /&gt;
This modified version will only exit when you press Escape, Alt-F4 or click on the &amp;quot;X&amp;quot; to close the window.&lt;br /&gt;
&lt;br /&gt;
==Syntax of Statements==&lt;br /&gt;
&lt;br /&gt;
;Each of the full screen processing statements has basically the same syntactical form, which can be divided into the following six portions:&lt;br /&gt;
:1.) Statement name and I/O location.&lt;br /&gt;
:2.) Field definition.&lt;br /&gt;
:3.) Attribute for current field (not applicable for PRINT FIELDS).&lt;br /&gt;
:4.) Field help specifications (not applicable for PRINT FIELDS).&lt;br /&gt;
:5.) I/O list.&lt;br /&gt;
:6.) Error condition list.&lt;br /&gt;
&lt;br /&gt;
Each of the above portions are identified in the following syntax diagram for full screen processing statements. This diagram will be used for reference in describing the syntax of four of the five of the full screen processing statements. (See the Statements chapter for a separate description of the PRINT FIELDS statement.) Since the following syntax diagram is rather complex, each portion will be described separately in the parameter descriptions that follow.&lt;br /&gt;
&lt;br /&gt;
[[Image:HELP0131.jpg]]&lt;br /&gt;
&lt;br /&gt;
;Defaults:&lt;br /&gt;
:1.) Display to and input from the screen.&lt;br /&gt;
:2.) Do not alter attribute when field is current.&lt;br /&gt;
:3.) Do not display field help text.&lt;br /&gt;
:4.) Interrupt the program if an error occurs and &amp;quot;ON error&amp;quot; is not active.&lt;br /&gt;
&lt;br /&gt;
===Statement Name I/O Location===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;Statement name, I/O location&amp;quot; portion of the syntax for full screen processing statements identifies the statement being used and the file number of the window which is to be used for input or output.&lt;br /&gt;
&lt;br /&gt;
;Parameters:&lt;br /&gt;
The primary keyword (&amp;quot;INPUT&amp;quot;, or &amp;quot;RINPUT&amp;quot;) of the statement being used must be specified first.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Wind-num&amp;quot; references a window file that has already been opened by an OPEN window statement. When this parameter is used, Business Rules input from and output to the window as if it were a separate screen: the first row and column of the window will be considered row 1 and column 1. When &amp;quot;wind-num&amp;quot; is not specified, file #0 (the full screen) is assumed.&lt;br /&gt;
&lt;br /&gt;
The secondary keyword (&amp;quot;FIELDS&amp;quot; or &amp;quot;SELECT&amp;quot;) of the statement being used must be specified next.&lt;br /&gt;
&lt;br /&gt;
===Field Definitions===&lt;br /&gt;
&lt;br /&gt;
;Purpose:&lt;br /&gt;
The &amp;quot;Field definition&amp;quot; portion of the full screen processing syntax identifies the following for each field: row number; column number; format type; length of the data to be input; and the monochrome, color and control attributes which are to be used for the field.&lt;br /&gt;
&lt;br /&gt;
;Comments and Examples:&lt;br /&gt;
The following statement identifies a field that starts at row 6, column 3. The field is numeric and has a total length of 7 characters, one of which is a decimal point and two of which are decimal digits. When the statement is executed on a monochrome monitor, the field will appear as reversed and highlighted. When the statement is executed on a color screen, the background of the field will be red. The name of the variable to which input will be assigned is AMOUNT.&lt;br /&gt;
&lt;br /&gt;
 00750 INPUT #7, FIELDS {\b &amp;quot;6,3,N 7.2,HRE/:R&amp;quot;}: AMOUNT&lt;br /&gt;
&lt;br /&gt;
;Insertable syntax (&amp;quot;field-spec&amp;quot; parameter):&lt;br /&gt;
[[Image:HELP0132.jpg]]&lt;br /&gt;
&lt;br /&gt;
;Defaults:&lt;br /&gt;
:1.) Fraction length = 0.&lt;br /&gt;
:2.) Maximum DIM length.&lt;br /&gt;
:3.) Use current attributes.&lt;br /&gt;
&lt;br /&gt;
;Parameters:&lt;br /&gt;
The above syntax may be inserted where the &amp;quot;field- spec&amp;quot; parameter appears in the main diagram for full screen processing statements. If the &amp;quot;string-expr&amp;quot; or &amp;quot;MAT string-array&amp;quot; parameters are used to specify the field definition, each must include the same elements that are identified in the &amp;quot;field-spec&amp;quot; parameter.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;row&amp;quot; parameter is a required integer value that indicates the row number on the screen or in the window where the data is to be entered.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;column&amp;quot; parameter is a required integer value that indicates the starting column number on the screen where data is to be entered.&lt;br /&gt;
&lt;br /&gt;
The next set of parameters specifies the format type of the data to be entered. There are three possible routes.&lt;br /&gt;
&lt;br /&gt;
In the top route, &amp;quot;num form-spec&amp;quot; represents a numeric format specification. G, GL, GU, GZ, L, N, and NZ are all valid in this position; the specification must be followed by a space. The &amp;quot;field-length&amp;quot; parameter, which is an integer value that identifies the length (including decimal point and decimal positions) of the field, comes next. If the field contains decimal positions, &amp;quot;fraction&amp;quot; should identify how many there are; this parameter must be separated from the field length by a period (no spaces).&lt;br /&gt;
&lt;br /&gt;
In the middle route, &amp;quot;string form-spec&amp;quot; represents a string format specification. C, CC, CR, CL, CU, G, GL, GU, GZ, V, VL and VU are all valid in this position. If &amp;quot;field length&amp;quot; is used, it must be separated from the format spec by a space.&lt;br /&gt;
&lt;br /&gt;
In the bottom route, the &amp;quot;PIC&amp;quot; or &amp;quot;FMT&amp;quot; specification allows you to specify a picture of the field to be input. See the File I/O chapter for a list of the characters that may be used in the &amp;quot;(pic-spec)&amp;quot; portion of this parameter.&lt;br /&gt;
&lt;br /&gt;
See the File I/O chapter and APPENDIX F Format Specification for additional information about all the format specifications listed in this section.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;attributes&amp;quot; parameter represents an insertable syntax that identifies the monochrome, color and control attributes that are to be used for the field. See the Definitions chapter for the insertable syntax and for additional information about this parameter.&lt;br /&gt;
&lt;br /&gt;
Additionally fonts can be controlled at the field level by assigning them to attribute substitution letters as in:&lt;br /&gt;
&lt;br /&gt;
:CONFIG ATTRIBUTE [J]RH,font=ariel,slant,max \line This specifies reverse highlight ariel italics maximum size.&lt;br /&gt;
&lt;br /&gt;
===Attribute for Current Field===&lt;br /&gt;
&lt;br /&gt;
;Purpose:&lt;br /&gt;
The &amp;quot;Attribute for current field&amp;quot; portion of the full screen processing syntax identifies the attribute that should be used for the field that the cursor is on.&lt;br /&gt;
&lt;br /&gt;
;Parameters:&lt;br /&gt;
The &amp;quot;ATTR&amp;quot; keyword identifies that a separate set of attributes should be used for the current input field. It must be followed by either the &amp;quot;attributes&amp;quot; parameter or the &amp;quot;string-expr&amp;quot; parameter. &amp;quot;Attributes&amp;quot; represents an insertable syntax that identifies the monochrome, color and control attributes that are to be used for the field. See the Definitions chapter for the insertable syntax and additional information.&lt;br /&gt;
&lt;br /&gt;
If used, the value of &amp;quot;string-expr&amp;quot; must include the same elements as are required in the &amp;quot;attributes&amp;quot; parameter.&lt;br /&gt;
&lt;br /&gt;
;Hot text&lt;br /&gt;
&lt;br /&gt;
Hot text may be displayed as either text or buttons. To make it display as a button, the trailing attribute specification should be a B followed by the fkey value to be returned when the button is clicked. e.g.  &amp;quot;20,5,CC 12,,B1244&amp;quot;  displayed as a button with fkey value 1244. Beginning with 4.16 the default behavior for a Hot Text field is a button.  The Button may be suppressed by using an &amp;quot;H&amp;quot; instead of the &amp;quot;B&amp;quot; or no letter.&lt;br /&gt;
&lt;br /&gt;
Any FIELDS text may now generate a keyboard scancode when double clicked, similar to the way BUTTONS and the Combo Box graphic do. Even linedraw characters can be &amp;quot;hot&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
When a picture is specified as an input field, all non-navigation keys are ignored.&lt;br /&gt;
&lt;br /&gt;
;Additionally fonts can be controlled at the field level by assigning them to attribute substitution letters as in:&lt;br /&gt;
&lt;br /&gt;
;The Shaded Sunken Appearance:&lt;br /&gt;
&lt;br /&gt;
Normal Windows behavior is to grey out any data entry fields that are inactive. Consequently, this is how labels appear when they are sunken. This defeats the ability to use a sunken approach to hilight screen headings. For example if you specify SCREEN U 0173 and then specify U as a leading attribute in a PRINT FIELDS specification, the field will be displayed sunken but grey.&lt;br /&gt;
&lt;br /&gt;
You can now override the normal Windows behavior with regard to greying out sunken labels by specifying S as a leading attribute. The S (sunken- formerly shaded) field attribute calls for a white sunken field irrespective of whether the field is open for input.&lt;br /&gt;
&lt;br /&gt;
;Fields Color Attributes&lt;br /&gt;
&lt;br /&gt;
In an input or print fields statement the attributes after the slash can only contain valid color attributes such as HRGB or #RRBBGG.  In the past the &amp;quot;N&amp;quot; attribute was accepted in this location.  Beginning with 4.17i any non-color attribute in the color section will generate an error.&lt;br /&gt;
&lt;br /&gt;
;Additional Date() Formats are supported:&lt;br /&gt;
&lt;br /&gt;
[NEWCELL(icells2l.spc)]&lt;br /&gt;
&lt;br /&gt;
Also, a new FIELDS format is supported&lt;br /&gt;
&lt;br /&gt;
 row,col,DATE(dd/mm/yy)&lt;br /&gt;
&lt;br /&gt;
The values are displayed as dates but are stored internally as day of century. The mask may be any valid DATE() mask.&lt;br /&gt;
&lt;br /&gt;
===Field Help Specifications===&lt;br /&gt;
&lt;br /&gt;
{{:Field Help}}&lt;br /&gt;
&lt;br /&gt;
===I/O List===&lt;br /&gt;
&lt;br /&gt;
;Purpose:&lt;br /&gt;
The &amp;quot;I/O list&amp;quot; portion of the syntax for full screen processing statements identifies the information that is to be input or output by the statement.&lt;br /&gt;
&lt;br /&gt;
;Comments and Examples:&lt;br /&gt;
With [[INPUT FIELDS]] and [[INPUT SELECT]], the I/O list identifies the variables to which the input is to be assigned. With [[RINPUT FIELDS]] and [[RINPUT SELECT]], the current values of the I/O list are output, the output values are updated by the operator, and the updated values are re-input to the specified variables.&lt;br /&gt;
&lt;br /&gt;
;Parameters:&lt;br /&gt;
A colon (:) must separate the I/O list from any previous parameters in the full screen processing statement. At least one &amp;quot;var-name&amp;quot; or &amp;quot;MAT array-name&amp;quot; must be specified. These two parameters represent the list of variables to be assigned values from the entered data. multiple variables must be separated by commas&lt;br /&gt;
&lt;br /&gt;
===Error-Cond List===&lt;br /&gt;
&lt;br /&gt;
;Purpose:&lt;br /&gt;
The &amp;quot;Error-cond list&amp;quot; portion of the full screen processing syntax identifies the types of errors that should be trapped and where execution should be transferred to when such errors are trapped.&lt;br /&gt;
&lt;br /&gt;
;Parameters:&lt;br /&gt;
The &amp;quot;error-cond&amp;quot; portion of the parameter identifies the error to be trapped, and the &amp;quot;line-ref&amp;quot; portion identifies the line to which execution should be transferred when such an error is trapped.&lt;br /&gt;
&lt;br /&gt;
;Technical Considerations:&lt;br /&gt;
1.) Relevant error Conditions are: CONV, Error, Exit, Help, IOERR, and SOFLOW.&lt;br /&gt;
&lt;br /&gt;
===Console Characteristics===&lt;br /&gt;
&lt;br /&gt;
The name of the directory with examples for this section is: EXAMPLES\CONSOLE&lt;br /&gt;
&lt;br /&gt;
A separate command console handles the operator commands without interfering with the graphical console. PRINT NEWPAGE outputs to the command console from the command prompt, and to both consoles in a program.  Regular PRINT statements and PRINT USING statements output only to the command console.  PRINT FIELDS and PRINT BORDER output only to the graphical console.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: CONSOLES.BR&lt;br /&gt;
&lt;br /&gt;
 00100 PRINT NEWPAGE ! output to both consoles&lt;br /&gt;
 00200 PRINT &amp;quot;ABC&amp;quot; ! output to command console&lt;br /&gt;
 00300 PRINT using &amp;quot;form C 4,C 2,N 2&amp;quot; : &amp;quot;WXYZ&amp;quot;,&amp;quot;  &amp;quot;,24! output to command console&lt;br /&gt;
 00400 PRINT FIELDS &amp;quot;15,4,C 15&amp;quot;: &amp;quot;million dollars&amp;quot;! output to graphical console&lt;br /&gt;
 00500 PRINT #0, BORDER &amp;quot;Billion dollars&amp;quot;:! output to graphical console&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
[[Image:NEWC0009.jpg]]&lt;br /&gt;
&lt;br /&gt;
The consoles can be opened to other than 80 columns by 25 rows:&lt;br /&gt;
&lt;br /&gt;
 OPEN #0: &amp;quot;Srow=5,Scol=5,Rows=30,Cols=100&amp;quot;, display, outin  ! specifies position on screen  and size in chars&lt;br /&gt;
&lt;br /&gt;
An OPEN window statement may specify a parent window number. In the following example, window #1 is opened as a child of window #0. Srow= and Scol= reference window #0.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: CHILDWIN.BR&lt;br /&gt;
&lt;br /&gt;
 00100 OPEN #1: &amp;quot;SROW=2,SCOL=2,EROW=5,ECOL=20,border=S,caption=MyWindow,Parent=0&amp;quot;,display, outin&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
[[Image:NEWC0010.jpg]]&lt;br /&gt;
&lt;br /&gt;
File numbers 300-999 are now supported in addition to those below 200.&lt;br /&gt;
&lt;br /&gt;
When a user defined Windows menu is displayed, the Preferences menu is suppressed. When the user-defined menu is cleared, Preferences is restored.&lt;br /&gt;
&lt;br /&gt;
===Datahilite===&lt;br /&gt;
&lt;br /&gt;
The default under Windows is to reverse each field as it is entered, and to position the cursor at the end of the data.This type of processing can be turned off, by specifying DATAHILITE OFF in the BRconfig.SYS file.&lt;br /&gt;
&lt;br /&gt;
;3D Fields applies to pre 4.0 versions:&lt;br /&gt;
&lt;br /&gt;
The 3D FIELDS parameter UNDR ON ignores all other attributes for underlined fields.  Traditionally, BR has used either the N (normal) attribute or R (reverse) attribute and ORed the other attributes specified (H, B, and U).  Now it works the same way, unless UNDR ON is specified and U is present, in which case it uses the U value exclusively.  This provides greater control over the color scheme with 3D FIELDS active.  You can use H for other purposes without affecting UH processing.&lt;br /&gt;
&lt;br /&gt;
{\b CONFIG DATAHILITE WIN ON} indicates that the user&#039;s Windows specifications for selected text should apply to highlited text.&lt;br /&gt;
&lt;br /&gt;
{\b CONFIG DATAHILITE WIN OFF} can be used to turn this feature off.&lt;br /&gt;
&lt;br /&gt;
;Two new parameters were added to the {\b DATAHILITE} configuration statement:&lt;br /&gt;
&lt;br /&gt;
XX Uses color attribute XX (as specified in a SCREEN statement) for the &amp;quot;selected&amp;quot; text.&amp;lt;br&amp;gt;&lt;br /&gt;
[X] Use the color specification associated with [X] in the ATTRIBUTE configuration statement.&lt;br /&gt;
&lt;br /&gt;
The 3D FIELDS parameter UNDR ON ignores all other attributes for underlined fields.  Traditionally, BR has used either the N (normal) attribute or R (reverse) attribute and ORed the other attributes specified (H, B, and U).  Now it works the same way, unless UNDR ON is specified and U is present, in which case it uses the U value exclusively.  This provides greater control over the color scheme with 3D FIELDS active.  You can use H for other purposes without affecting UH processing.&lt;br /&gt;
&lt;br /&gt;
FIELDS3D has been changed to 3D FIELDS.&lt;br /&gt;
&lt;br /&gt;
When 3D fields are enabled, the screen requires more vertical space than it otherwise would.  Therefore, the BR window takes on a rectangular appearance, and doesn&#039;t look as good in full screen mode as it does as a window.&lt;br /&gt;
&lt;br /&gt;
To activate this feature, specify 3D FIELDS ON in your BRconfig.SYS file and also specify SCREEN U 0170 only.  (Or specify SCREEN U 0170,N 38 to add some flair.)&lt;br /&gt;
&lt;br /&gt;
You can also use the CONFIG command to activate this feature, but if so, you must OPEN #0: &amp;quot;SROW=1,SCOL=1&amp;quot;, DISPLAY, OUTIN after executing the CONFIG command.&lt;br /&gt;
&lt;br /&gt;
Specifying 3D FIELDS in BRconfig.SYS is not sufficient to activate it.  You must also either precede a standard field attribute with 01 (e.g. U 0170) or specify S as a leading field attribute in your FIELDS specification.&lt;br /&gt;
&lt;br /&gt;
e.g. INPUT FIELDS &amp;quot;10,10,C 10,S&amp;quot;: X$   The preferred way for legacy code to activate 3D FIELDS is to use the SCREEN statement.&lt;br /&gt;
&lt;br /&gt;
Ctrl-Z will act as undo when DATAHILITE is on.&lt;br /&gt;
&lt;br /&gt;
===Window on a field===&lt;br /&gt;
&lt;br /&gt;
The ability to enter and process more text than a window field can display at one time is included.  This is a very powerful feature that lays the groundwork for support of proportional fonts.  You will, for example, be able to enter and maintain fifty characters of data in a twenty character field on any platform.&lt;br /&gt;
&lt;br /&gt;
;The syntax for enabling this capability is:&lt;br /&gt;
&lt;br /&gt;
 00100 INPUT FIELDS &amp;quot;row,col,20/C 50,N/W:W&amp;quot;: name$&lt;br /&gt;
&lt;br /&gt;
Which enables up to 50 characters to be entered into a 20 character window field.&lt;br /&gt;
&lt;br /&gt;
;This syntax was changed in the July release of BR 4.17. It used to be:&lt;br /&gt;
&lt;br /&gt;
 00100 INPUT FIELDS &amp;quot;row,col,C 20/50,N/W:W&amp;quot;&lt;br /&gt;
&lt;br /&gt;
where 20 was the width of the &amp;quot;window&amp;quot; and 50 was the allowed width of the data entry field.  4.17 changed this so that it would be compatable with more format statements (and easier to understand).&lt;br /&gt;
&lt;br /&gt;
This new syntax also works for&lt;br /&gt;
&lt;br /&gt;
 00100 INPUT FIELDS &amp;quot;row,col,20/PIC(##D##D##),N/W:W&amp;quot;&lt;br /&gt;
&lt;br /&gt;
and for&lt;br /&gt;
&lt;br /&gt;
 00100 INPUT FIELDS &amp;quot;row,col,date(M3 d, cy),N/W:W&amp;quot;&lt;br /&gt;
&lt;br /&gt;
;Window on a field processing&lt;br /&gt;
&lt;br /&gt;
The syntax for window on a field has been changed to accommodate more options.  The displayed length of the field is the first parameter and precedes the field size.&lt;br /&gt;
&lt;br /&gt;
 01200 RINPUT FIELDS &amp;quot;10,20,40/C 100,N/W:W&amp;quot;:TEXT$&lt;br /&gt;
&lt;br /&gt;
Displays and allows input to a 100 character variable TEXT$ in a field that only uses 40 characters of screen space.&lt;br /&gt;
&lt;br /&gt;
To accommodate extended field capacities with &#039;display under mask&#039; field types, BR now uses the following syntax:&lt;br /&gt;
&lt;br /&gt;
row,col,displayed-length/field-spec&amp;lt;br&amp;gt;&lt;br /&gt;
e.g.   5,10,7/N 10.2&amp;lt;br&amp;gt;&lt;br /&gt;
          - 10 digits with decimal point displayed in 7 char field&amp;lt;br&amp;gt;&lt;br /&gt;
 or    7,10,11/PIC(#,###.##)&amp;lt;br&amp;gt;&lt;br /&gt;
          - 7 digits with punctuation displayed in 11 char field&lt;br /&gt;
&lt;br /&gt;
The main use for this feature is to shorten the displayed length when displaying proportional fonts as in  9,10,20/C 25.&lt;br /&gt;
&lt;br /&gt;
OPTION 45 allows the old method of extended field specification in addition to the new method. If the older format is encountered without Option 45, then error 861 is reported.&lt;br /&gt;
&lt;br /&gt;
;Text Box&lt;br /&gt;
&lt;br /&gt;
A text box with wrapping text can be displayed in BR if the field is opened in a window.  An input fields statement that exceeds the width of the window will wrap within the window and allow data entry in wrapping format.&lt;br /&gt;
&lt;br /&gt;
 00100 OPEN #10:&amp;quot;SROW=10,SCOL=10,ROWS=5,COLS=20&amp;quot;,DISPLAY,OUTIN&lt;br /&gt;
 00200 RINPUT #10,FIELDS &amp;quot;1,1,100/C 2000,N/W:W&amp;quot;:TEXT$&lt;br /&gt;
&lt;br /&gt;
Will wrap an input fields data entry field within a window that is 5 rows by 20 columns.  This window will wrap and scroll to allow entry of a 2000 character string.&lt;br /&gt;
&lt;br /&gt;
==Graphical Controls==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Graphical Controls&#039;&#039;&#039; are also known as [[Widgets]].&lt;br /&gt;
&lt;br /&gt;
RADIO Buttons and CHECK Boxes will work with PRINT, INPUT, and RINPUT operations.  A COMBO box may be processed with separate PRINT and INPUT statements. A COMBO box may also be processed with RINPUT after it has been populated with a PRINT statement. However, GRID and LIST require separate PRINT and INPUT statements and do not allow RINPUT.&lt;br /&gt;
&lt;br /&gt;
===Combo Boxes===&lt;br /&gt;
&lt;br /&gt;
{{:Combo Boxes}}&lt;br /&gt;
&lt;br /&gt;
===Check Boxes===&lt;br /&gt;
&lt;br /&gt;
{{:Check Boxes}}&lt;br /&gt;
&lt;br /&gt;
===Radio Buttons===&lt;br /&gt;
{{:Radio Buttons}}&lt;br /&gt;
&lt;br /&gt;
===MsgBox System Function===&lt;br /&gt;
See [[Internal Functions]]&lt;br /&gt;
&lt;br /&gt;
===Combo Box Character===&lt;br /&gt;
&lt;br /&gt;
The combo box character is supported like it is by the old console (Q as a leading attribute plus specification of an fkey value).&lt;br /&gt;
&lt;br /&gt;
===Fonts And No Graphical Control Overlap===&lt;br /&gt;
&lt;br /&gt;
Labels (strings) displayed are analyzed for spacing.. If more than one blank is found or line draw or underline characters are encountered, a new control delineated. When such individual controls (label segments) are delineated the last non-whitespace character of each subfield is check for a colon. If it is a colon the text is right justified within the subfield.&lt;br /&gt;
&lt;br /&gt;
IF A CHR$(5) CHARACTER IS ENCOUNTERED IT IS REPLACED BY A BLANK AND A CONTROL BOUNDARY IS DELINEATED.  Delineating a new control implies continuing output at the position where it would be if the window were using fixed fonts. This can be useful when setting up a multi-column INPUT SELECT line. This is because INPUT SELECT does not align columns with proportional fonts the way PRINT FIELDS does. Ultimately it will be preferable to recode your lookups or zooms to use LISTview.&lt;br /&gt;
&lt;br /&gt;
One of the disappointments in moving to a GUI presentation is that text selection via DATAHILITE overrides the ATTR value. ATTR still applies to the &#039;current&#039; field (where the cursor is) once the data is deselected. But the color of selected text is necessarily determined by the operating environment. If you want to override the color of the current field immediately upon entry, then you will have to turn DATAHILITE off so the text will not be selected upon entry.&lt;br /&gt;
&lt;br /&gt;
===New Fonts and No Graphical Control Overlap===&lt;br /&gt;
&lt;br /&gt;
All window displays consist of true Windows/MAC graphical controls and *proportional fonts* are supported along with non-proportional fonts. Proportional fonts are not rendered fixed size. Positioning of controls and sizing of controls on the window are managed by simulating fixed character positions.&lt;br /&gt;
&lt;br /&gt;
Hot text may be displayed as either text or buttons. The default is to display it as a button.  To override this default the trailing attribute specification should be an H followed by the fkey value to be returned when the button is clicked.&lt;br /&gt;
&lt;br /&gt;
e.g.&lt;br /&gt;
   &amp;quot;20,5,CC 12,,B1244&amp;quot;  displayed as a button with fkey value 1244&lt;br /&gt;
   &amp;quot;20,5,CC 12,,H1244&amp;quot;  displayed NOT as a button but returns fkey value 1244&lt;br /&gt;
&lt;br /&gt;
The cursor changes to a hand pointer only for hot text and pictures. It does not do so for buttons because it is self evident that they are hot.&lt;br /&gt;
&lt;br /&gt;
Labels (captions) displayed are analyzed for spacing.. If more than one blank is found or line draw or underline characters are encountered, a new control is delineated. When such individual controls (label segments) are delineated the last non-whitespace character of each subfield is check for a colon. If it is a colon, the text is right justified within the SUBfield. In other words, the colon right aligns the data WHERE IT WOULD BE IF YOU WERE USING FIXED WIDTH FONTS.&lt;br /&gt;
&lt;br /&gt;
IF A CHR$(5) CHARACTER IS ENCOUNTERED IT IS REPLACED BY A BLANK AND A CONTROL BOUNDARY IS DELINEATED.  Delineating a new control implies continuing output at the position where it would be if the window were using fixed fonts.&lt;br /&gt;
&lt;br /&gt;
One of the disappointments in moving to a GUI presentation is that text selection via DATAHILITE overrides the ATTR value. ATTR still applies to the &#039;current&#039; field (where the cursor is) once the data is deselected. But the color of selected text is necessarily determined by the operating environment. If you want to override the color of the current field immediately upon entry, then you will have to turn DATAHILITE off so the text will not be selected upon entry.&lt;br /&gt;
&lt;br /&gt;
The new GUI mode does NOT permit bleed through the way the old console does. That is, anything written to an underlying window is not seen until the overlaying window is removed. However, a capability called FORCE VISIBILITY (described in a separate section) overcomes this limitation for programs designed for the old BR console.&lt;br /&gt;
&lt;br /&gt;
If a field is written to a window that already has an overlapping field, then the field which it overlaps (the original field on the screen) is automatically deleted. There are a couple of exceptions to this:&lt;br /&gt;
&lt;br /&gt;
:1.) LABELs printed to a window using a proportional font can sometimes extend beyond the area that they would occupy as a fixed width font. When this happens, BR extends the control (text space) to the length of the proportional data. If a label is subsequently displayed in this extended area, it is positioned *under* the previous label extension.  If a non-label is printed in this extended area, it is positioned *over* the label extension.&lt;br /&gt;
&lt;br /&gt;
:2.) If a control is displayed over the top of a LABEL&#039;s fixed character space, the original label is stenciled (or truncated) by the new control using fixed width font logic. Each resulting segment is handled independently as if it were a separate field.&lt;br /&gt;
&lt;br /&gt;
===Grid and List===&lt;br /&gt;
{{:Grid and List}}&lt;br /&gt;
&lt;br /&gt;
===Tabs===&lt;br /&gt;
&lt;br /&gt;
{{:Tabs}}&lt;br /&gt;
&lt;br /&gt;
==Font Control and Color Specification==&lt;br /&gt;
&lt;br /&gt;
===Attribute Substitution Name Extensions===&lt;br /&gt;
&lt;br /&gt;
Field Attribute [xxx] identifiers may now be up to 12 characters long.&lt;br /&gt;
&lt;br /&gt;
Fonts can be specified at the field level by assigning them to attribute substitution names as in:&lt;br /&gt;
&lt;br /&gt;
 CONFIG ATTRIBUTE [hilite_text]  font=ariel:slant:max&lt;br /&gt;
 or -&lt;br /&gt;
 CONFIG ATTRIBUTE [hilite_text] /#rrggbb:#rrggbb, font=ariel:slant:max&lt;br /&gt;
&lt;br /&gt;
This specifies that [hilite_text] denotes foreground and background colors specified as #rrggbb with ariel, italics, and maximum size font.&lt;br /&gt;
&lt;br /&gt;
===Screen and Attribute Statements Working Together===&lt;br /&gt;
&lt;br /&gt;
SCREEN statements can now use [config-attribute] references.  So the preferred (more readable) method for specifying screen attributes is to first define each attribute in full words and then reference them in SCREEN statements.  This means that the ATTRIBUTES and SCREEN statements can be used to progressively build upon one another:&lt;br /&gt;
&lt;br /&gt;
:e.g.    ATTRIBUTES [lite_blue] /#112233:#556677 (This sets lite_blue foreground and background in terms of HTML color specifications.)&lt;br /&gt;
&lt;br /&gt;
 ATTRIBUTES [yellow] /#442255:#887799&lt;br /&gt;
 SCREEN N [lite_blue], U [yellow]&lt;br /&gt;
&lt;br /&gt;
   - or better yet -&lt;br /&gt;
 ATTRIBUTES [normal] /W:W    ! windows foreground and background&lt;br /&gt;
 SCREEN N [normal]&lt;br /&gt;
&lt;br /&gt;
 ATTRIBUTES [XX] UH, font=ariel:slant:max&lt;br /&gt;
- or -&lt;br /&gt;
 ATTRIBUTES [XX] UH/RGB:W, font=ariel:slant:max&lt;br /&gt;
&lt;br /&gt;
On Unix terminals the above statement would be interpreted as:&lt;br /&gt;
&lt;br /&gt;
 ATTRIBUTES [XX] UH/RGB:&lt;br /&gt;
because W and font= are ignored by Unix terminal support.&lt;br /&gt;
&lt;br /&gt;
Now that I have defined [XX] and other attributes I can specify:&lt;br /&gt;
 SCREEN N [lite_blue], U [yellow], R [XX]&lt;br /&gt;
&lt;br /&gt;
The /xxx:yyy embedded syntax in FIELDS statements still overrides color for the corresponding fields, irrespective of SCREEN settings. However, now a W may be specified in lieu of R, G, B and H, denoting use the current Windows colors.&amp;lt;br&amp;gt;&lt;br /&gt;
e.g. /RG:W     -  yellow foreground on Windows background color&lt;br /&gt;
&lt;br /&gt;
Colors may now be specified as /#rrggbb:#rrggbb where rr is the red gradient, gg is the green and bb is the blue gradient. Or they may be specified as /W:W which denotes Windows foreground and background.&lt;br /&gt;
&lt;br /&gt;
The *background* color attribute T denotes Transparent and applies ONLY TO LABELS (PRINT FIELDS and INPUT SELECT). This is useful for printing text over various backgrounds without showing the Windows grey around the letters.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
 00080 Execute &amp;quot;CONFIG ATTRIBUTE [mickey_mouse] /#rrggbb:T, font=ariel:slant:max&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00090 Execute &amp;quot;CONFIG ATTRIBUTE [hilite_text] /#rrggbb:#rrggbb, font=ariel:slant:max&amp;quot;&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;12,22,25/C 20,[hilite_text],520&amp;quot;: &amp;quot;My Text&amp;quot;&lt;br /&gt;
&lt;br /&gt;
This example displays up to 25 proportional characters in a 20 fixed character space in specific colors with ariel italics, maximum size, and a hot key (fkey) value of 520.&lt;br /&gt;
&lt;br /&gt;
===Font Characteristics===&lt;br /&gt;
&lt;br /&gt;
Multiple fonts are now supported under Windows.  The default is still SYSTEMPC.  Any installed non-proportional font is permitted that is named in the following BRCONFIG.SYS statement:&lt;br /&gt;
&lt;br /&gt;
FONT {fontname} ...&lt;br /&gt;
&lt;br /&gt;
Some of the fonts provided by Microsoft are:&lt;br /&gt;
&lt;br /&gt;
Lucida Console&amp;lt;br&amp;gt;&lt;br /&gt;
Minitel&amp;lt;br&amp;gt;&lt;br /&gt;
Terminal (NT only)&amp;lt;br&amp;gt;&lt;br /&gt;
some Courier fonts&lt;br /&gt;
&lt;br /&gt;
Version 3.9+ supports the command STATUS FONTS.  This will report all currently installed Windows fonts that are acceptable in a FONT statement.  Also fonts can be changed dynamically via the CONFIG command.&lt;br /&gt;
&lt;br /&gt;
FONT=&amp;lt;br&amp;gt;&lt;br /&gt;
FONT.TEXT=&amp;lt;br&amp;gt;&lt;br /&gt;
FONT.LABELS=&amp;lt;br&amp;gt;&lt;br /&gt;
FONT.BUTTONS=&lt;br /&gt;
&lt;br /&gt;
The default font is the user&#039;s Windows default font. Separate fonts may be invoked for Labels, Buttons, and Text (data entry fields):&lt;br /&gt;
&lt;br /&gt;
 00100 OPEN #0: &amp;quot;Srow=5,Scol=5,Rows=25,Cols=80,FONT=Arial&amp;quot;, Display, Outin&lt;br /&gt;
:|(specifies a base font)&lt;br /&gt;
 00200 OPEN #0: &amp;quot;Rows=30,Cols=100,FONT.LABELS=Terminal,FONT.TEXT=Arial,FONT.BUTTONS=Lucida Console&amp;quot;, Display, Outin&lt;br /&gt;
&lt;br /&gt;
You can also specify the following font qualifiers:&lt;br /&gt;
&lt;br /&gt;
:1.) Family: decor / roman / script / swiss / modern (fixed)(this may be useful in addition to the font name for systems that don&#039;t have the specified font)&lt;br /&gt;
:2.) Boldness: light / bold&lt;br /&gt;
:3.) Style: ital / slant&lt;br /&gt;
:4.) Underline: under&lt;br /&gt;
:5.) Size: small / medium / large / max - Size is based entirely on font height.&lt;br /&gt;
&lt;br /&gt;
Font Size Adjustment- adjusts the size of displayed text down to accommodate the horizontal space allocated for the text:&lt;br /&gt;
:1.) Width:  Determine the average caps + digits character width. Then reduce the font size until the number of characters times this average fits the BR field capacity.&lt;br /&gt;
:2.) Width+: Determine the average letter size considering both upper and lower case letters. Then apply the width limitation.&lt;br /&gt;
:3.) Width-: Make the font small enough to accommodate a string of capital W&#039;s.&lt;br /&gt;
:4.) NoWidth: Clear the Width adjustment setting.&lt;br /&gt;
&lt;br /&gt;
 e.g.&lt;br /&gt;
 00100 OPEN #0: &amp;quot;Rows=30,Cols=100,FONT.LABELS=Terminal:bold:slant,FONT.TEXT=Arial,FONT.BUTTONS=Lucida Console&amp;quot;,Display,Outin&lt;br /&gt;
&lt;br /&gt;
The former FONTSIZE=99x99 parameter is only supported when GUI is OFF.&lt;br /&gt;
&lt;br /&gt;
Note that the maximum length of configuration statements has been increased to 800 bytes.&lt;br /&gt;
&lt;br /&gt;
Also any open window statement will inherit the attributes of the parent window, except that any new font specification (labels / text / buttons) will begin with the system defaults for that category.&lt;br /&gt;
&lt;br /&gt;
===Hot Text===&lt;br /&gt;
&lt;br /&gt;
Any FIELDS text may now generate a keyboard scancode when double clicked, similar to the way Buttons and the Combo Box graphic do.&lt;br /&gt;
&lt;br /&gt;
Hot field fkey values may now be in the ranges:&lt;br /&gt;
*  0-99&amp;lt;br&amp;gt;&lt;br /&gt;
*  1000-9999&amp;lt;br&amp;gt;&lt;br /&gt;
A special hot-key value of 10000 denotes the Enter key, and generates a zero fkey value.&lt;br /&gt;
&lt;br /&gt;
Linedraw can also be marked as HOT.&lt;br /&gt;
&lt;br /&gt;
===HTML Color Parameters===&lt;br /&gt;
&lt;br /&gt;
;RRGGBB, W, and T Color Specifications&lt;br /&gt;
&lt;br /&gt;
Colors may now be specified as /#rrggbb:#rrggbb where rr is the red gradient, gg is the green and bb is the blue gradient. Or they may be specified as /W:W which denotes Windows foreground and background.&lt;br /&gt;
&lt;br /&gt;
The *background* color attribute T denotes Transparent and applies ONLY TO LABELS (PRINT FIELDS and INPUT SELECT). This is useful for printing text over various backgrounds without showing the Windows grey around the letters.&lt;br /&gt;
&lt;br /&gt;
;Examples:&lt;br /&gt;
&lt;br /&gt;
 00100 EXECUTE &amp;quot;CONFIG ATTRIBUTE [hilite_text]/#442255:T font=arial:slant:max&amp;quot;&lt;br /&gt;
 00200 OPEN #0: &amp;quot;caption=mikhail, Picture=Matrix.ico:NORESIZE&amp;quot;,display,outin&lt;br /&gt;
 00300 PRINT FIELDS &amp;quot;12,40,C 20/25,[hilite_text],80&amp;quot;: &amp;quot;Text is twenty chars&amp;quot;&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
[[Image:NEWC0015.jpg]]&lt;br /&gt;
&lt;br /&gt;
This example displays up to 25 proportional characters in a 20 fixed character space in specific colors with arial italics, maximum size, and a hot key (FKEY) value of 80.&amp;lt;br&amp;gt;&lt;br /&gt;
IMPORTANT: Spaces are not allowed between the attribute substitution name [hilite_text] and the attribute combination /#442255:T.  However, spaces are allowed between the attribute substitution name and the font specification. Also, do not put a comma after the attribute combination /#442255:T.&lt;br /&gt;
&lt;br /&gt;
===Command Console Colors===&lt;br /&gt;
&lt;br /&gt;
The SCREEN C xx specification determines the foreground and background colors of the command console.&lt;br /&gt;
&lt;br /&gt;
===Windows Color and Font Settings===&lt;br /&gt;
&lt;br /&gt;
;The current Windows color and font settings may be retrieved:&lt;br /&gt;
[NEWCELL(icells2l.spc)]&lt;br /&gt;
&lt;br /&gt;
;For a complete list of possible values and current settings type:&lt;br /&gt;
&lt;br /&gt;
 STATUS ENV&lt;br /&gt;
&lt;br /&gt;
==Graphical Interface==&lt;br /&gt;
&lt;br /&gt;
{{:Graphic User Interface}}&lt;br /&gt;
&lt;br /&gt;
==Menu Support==&lt;br /&gt;
&lt;br /&gt;
{{:Menu}}&lt;br /&gt;
&lt;br /&gt;
==Keyboard Responses==&lt;br /&gt;
&lt;br /&gt;
[[Image:NEWC0044.jpg]]&lt;br /&gt;
&lt;br /&gt;
GRID Navigation Mode: -----------------------------------------------&amp;lt;br&amp;gt;&lt;br /&gt;
[[Image:NEWC0046.jpg]]&lt;br /&gt;
&lt;br /&gt;
GRID Edit Mode: -----------------------------------------------------&amp;lt;br&amp;gt;&lt;br /&gt;
[[Image:NEWC0047.jpg]]&lt;br /&gt;
&lt;br /&gt;
===Conditional On Datahilite===&lt;br /&gt;
&lt;br /&gt;
[[Image:NEWC0048.jpg]]&lt;br /&gt;
&lt;br /&gt;
===Insert Behavior===&lt;br /&gt;
 &lt;br /&gt;
 Insert defaults to being on if datahilite is on and off if datahilite is off.  It can be configured separately.&lt;br /&gt;
 &lt;br /&gt;
===FKEY Value:===&lt;br /&gt;
:  90 Page up&lt;br /&gt;
:  91 Page down&lt;br /&gt;
:  99 Escape key&lt;br /&gt;
: 100 Right click (Help)&lt;br /&gt;
: 102 Up arrow&lt;br /&gt;
: 103 Down Arrow&lt;br /&gt;
: 104 Right Arrow (in Grid)&lt;br /&gt;
: 105 Up Field&lt;br /&gt;
: 106 Down Field&lt;br /&gt;
: 107 Field Overflow&lt;br /&gt;
: 109 Right Arrow&lt;br /&gt;
: 112 Home Key&lt;br /&gt;
: 113 End Key&lt;br /&gt;
: 114 Field Plus (Numeric Pad)&lt;br /&gt;
: 115 Field Minus (Numeric Pad)&lt;br /&gt;
: 116 Right Arrow field exit via right arrow positions to first char of next field similar to down arrow&lt;br /&gt;
&lt;br /&gt;
:&lt;br /&gt;
==Display Buttons==&lt;br /&gt;
&lt;br /&gt;
{{:Display Buttons}}&lt;br /&gt;
&lt;br /&gt;
==Mouse Response==&lt;br /&gt;
&lt;br /&gt;
(only effective during INPUT operation)&lt;br /&gt;
&lt;br /&gt;
FKEY has two new values 200 and 201 that may occur on a field with an X attribute.  This may affect your programs.  These values are returned when the mouse is used to exit a field.&lt;br /&gt;
&lt;br /&gt;
Mouse Buttons can be remapped with a brconfig.sys keyboard statement.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot;&lt;br /&gt;
| &#039;&#039;&#039;|| Single Left Click &#039;&#039;&#039;|| Double Left Click&lt;br /&gt;
|-&lt;br /&gt;
|Without X &#039;&#039;&#039;||Move Cursor &#039;&#039;&#039;||Move and Enter&lt;br /&gt;
|-&lt;br /&gt;
|With X &#039;&#039;&#039;||Exit Field with  201 Fkey Value &#039;&#039;&#039;|| Exit Field with 202 Fkey Value&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
These exits imply that the cursor will resume at the field that is clicked upon.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot;&lt;br /&gt;
| &#039;&#039;&#039;||Single Right Click &#039;&#039;&#039;|| Double Right Click&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;||Exit the field with Fkey Value of 100 (same as help key)&#039;&#039;&#039;||Move to new location and Exit the 100 Fkey Value&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Windows Clipboard Support==&lt;br /&gt;
&lt;br /&gt;
Windows Clipboard Support (Cut, Copy, and Paste)&lt;br /&gt;
&lt;br /&gt;
Windows Cut and Paste&lt;br /&gt;
&lt;br /&gt;
Mark a string using [[Shift+Arrow]] keys, then&amp;lt;br&amp;gt;&lt;br /&gt;
[[Ctrl+X]] to cut the string to the Windows clipboard,&amp;lt;br&amp;gt;&lt;br /&gt;
[[Ctrl+C]] to copy the string to the Windows clipboard,&amp;lt;br&amp;gt;&lt;br /&gt;
[[Ctrl+V]] to paste the string from the Windows clipboard&amp;lt;br&amp;gt;&lt;br /&gt;
([[Ctrl+A]] replaces Ctrl+C for breaking into a program.)&lt;br /&gt;
&lt;br /&gt;
The above are all standard Windows keystrokes for cut &amp;amp; paste. Cut and Paste may be used within BR and between BR and other applications.&lt;br /&gt;
&lt;br /&gt;
If a field is highlighted, paste will only paste to that field. Otherwise, BR pastes one line per field, starting with the current field and to all subsequent fields, until the last field, or last line, whichever comes first.&lt;br /&gt;
&lt;br /&gt;
{{Future|A future release will support the mouse for marking text.}}&lt;br /&gt;
&lt;br /&gt;
==WBTerm Compiler==&lt;br /&gt;
&lt;br /&gt;
Business Rules require a [[wbterm.out]] file which is produced by the wbterm compiler release 5.1 or greater. This is due to significant changes made to the way Business Rules can access scancodes from a terminal&#039;s keyboard. All old wbterm.in files should recompile successfully, but we urge you to edit any existing wbterm.in files to use the new syntax which is available. Since older versions of the wbterm.out file will not operate on this release, your wbterm.in file must be recompiled using this release 5.1 wbterm compiler.&lt;br /&gt;
&lt;br /&gt;
The old method of showing scancodes in the wbterm.in file was to specify up to two significant hex digits followed by a value indicating the number of extra characters to throw away. While this method is still supported, it is not adequate for some machines such as the IBM RT, where some significant digits were being thrown away. This method allows you to specify up to 20 significant digits.&lt;br /&gt;
&lt;br /&gt;
For example, on an AIX console, the F1 key returns the following values: 1B 5B 30 30 31 71. With the old method, this scancode would be specified in the wbterm.in file as F1=5B30,3;.&lt;br /&gt;
&lt;br /&gt;
With this method, the string returned by this key is specified as F1=1B5B30303171;. This method also supports special characters such as \033 for escape, thus the F1 string could also be coded as F1=&amp;quot;\033[001q&amp;quot;. Note that the escape value (&amp;quot;1B&amp;quot; in the first example and &amp;quot;\033&amp;quot; in the second) is included in this method string, whereas it was not with the old method.&lt;br /&gt;
&lt;br /&gt;
Support for remapping alphanumeric keys has also been added. For example, if you wish to remap a particular keystroke for a wbterm.in keyword, you may use the following format: F10=&amp;quot;+&amp;quot;;. This example would, of course, disable the use of the + key to type the &amp;quot;+&amp;quot; character, but this syntax can be useful for inserting the actual ASCII text that a key is returning.&lt;br /&gt;
&lt;br /&gt;
{\b Color=5 Spec in WBTerm}&lt;br /&gt;
&lt;br /&gt;
COLOR=5 has been added to the Unix/Xenix wbterm.in file (release 1.4 or greater) to support color consoles, particularly Interactive 386/ix and AIX consoles.&lt;br /&gt;
&lt;br /&gt;
To learn how this  specification works, you must first understand that when COLOR=5 is specified, Business Rules will send color information to the operating system in the following escape sequence format:  esc[0;3c;4c(;h)(;b)m. In this sequence, 3c represents the foreground color, 4c represents the background color, and &amp;quot;(;h)&amp;quot; and &amp;quot;(;b)&amp;quot; are optional parameters for highlight and blink. Business Rules determines the values for c, h and b by referring to the COLOR=5 spec.&lt;br /&gt;
&lt;br /&gt;
The format of the COLOR=5 specification is as follows:&lt;br /&gt;
&lt;br /&gt;
COLOR=5, &amp;quot;cccccccchb&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
The 10 positions in the above quoted string correspond to the 10 display attributes in the following IBM PC color table:&lt;br /&gt;
&lt;br /&gt;
0 - black&amp;lt;br&amp;gt;&lt;br /&gt;
1 - blue&amp;lt;br&amp;gt;&lt;br /&gt;
2 - green&amp;lt;br&amp;gt;&lt;br /&gt;
3 - cyan (blue+green)&amp;lt;br&amp;gt;&lt;br /&gt;
4 - red&amp;lt;br&amp;gt;&lt;br /&gt;
5 - magenta (red+blue)&amp;lt;br&amp;gt;&lt;br /&gt;
6 - brown (red+green)&amp;lt;br&amp;gt;&lt;br /&gt;
7 - white (red+blue+green)&amp;lt;br&amp;gt;&lt;br /&gt;
8 - highlight&amp;lt;br&amp;gt;&lt;br /&gt;
9 - blink&lt;br /&gt;
&lt;br /&gt;
To create the COLOR=5 specification for any terminal, you must order the first eight digits according to the way in which their colors appear in the terminal&#039;s color table (this information should be available in the terminal documentation). The last two digits identify the escape character that must be sent for highlight and blink, respectively. The following specification, for instance, indicates that black (0) is the first color in the AT386 color table, red (4) is the second, green (2) is the third, and so on. The last two characters in the string identify that 1 is the code to use for highlight, and 5 is the code to use for blink.&lt;br /&gt;
&lt;br /&gt;
 COLOR=5, &amp;quot;0426153715&amp;quot;;       ! actual AT386 entry&lt;br /&gt;
&lt;br /&gt;
To display a red foreground on a black background, Business Rules would refer to the above COLOR=5 spec and note that red (4) is in the 1 position of the 10-digit string, while black (0) is in the 0 position of the 10-digit string. Therefore, it would send an escape sequence of esc[0;31;40m to the operating system. (It uses a value of 1 for the foreground c and a value of 0 for the background c values in the esc[0;3c;4c(;h)(;b)m character sequence.)&lt;br /&gt;
&lt;br /&gt;
Likewise, to display a magenta foreground on a blue background with blink, Business Rules would see that magenta (5) is in the 6 position, blue (1) is in the 5 position, and that the value of the blink position is 5 in the COLOR=5 string. Therefore, it would send an escape sequence of esc[0;35;45;5m.&lt;br /&gt;
&lt;br /&gt;
Some additional examples of escape sequences that Business Rules would send based on the above COLOR=5 spec for the AT386 are as follows:&lt;br /&gt;
&lt;br /&gt;
esc[0;31;40;5m        - red on black with blink&amp;lt;br&amp;gt;&lt;br /&gt;
esc[0;35;40m          - red+blue on black&amp;lt;br&amp;gt;&lt;br /&gt;
esc[0;30;48;1m        - black on white with highlight&lt;br /&gt;
&lt;br /&gt;
==Setting Display attributes with PRINT HEX$==&lt;br /&gt;
&lt;br /&gt;
In addition to using full screen processing, there are two ways to set the display attributes on a screen: with the PRINT HEX$(&amp;quot;1Bxx&amp;quot;) method and with the PRINT HEX$(&amp;quot;Ex&amp;quot;) method. In all cases, a PRINT [[NEWPAGE]] statement clears the screen and sets all character attributes to normal. Some display combinations may not be supported on all machines.&lt;br /&gt;
&lt;br /&gt;
===Print HEX$(&amp;quot;1Bxx&amp;quot;)===&lt;br /&gt;
&lt;br /&gt;
;Visual attributes of the display screen can be changed by executing the following statement:&lt;br /&gt;
&lt;br /&gt;
PRINT HEX$(&amp;quot;1Bxx&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
In this example, xx represents the machine-dependent hexadecimal representation of the display attribute. This method is not portable between equipment types. Once such a statement is executed, Business Rules uses the specified attribute for all screen output until a new PRINT HEX$(&amp;quot;1Bxx&amp;quot;) statement (or a PRINT NEWPAGE) is executed.&amp;lt;br&amp;gt;&lt;br /&gt;
The bit configuration for xx on the IBM PC is as follows:&lt;br /&gt;
&lt;br /&gt;
[[Image:HELP0136.jpg]]&lt;br /&gt;
&lt;br /&gt;
===Print Hex$(&amp;quot;Ex&amp;quot;)===&lt;br /&gt;
&lt;br /&gt;
PRINT HEX$(&amp;quot;Ex&amp;quot;)&amp;lt;br&amp;gt;&lt;br /&gt;
Another method of setting visual attributes of the display screen is to execute the statement PRINT HEX$(&amp;quot;Ex&amp;quot;), where x is:&lt;br /&gt;
&lt;br /&gt;
{|border=1&lt;br /&gt;
|0||normal screen&lt;br /&gt;
|-&lt;br /&gt;
|1||underline&lt;br /&gt;
|-&lt;br /&gt;
|2||blink&amp;lt;br&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|3||invisible&lt;br /&gt;
|-&lt;br /&gt;
|4||highlight&lt;br /&gt;
|-&lt;br /&gt;
|5||highlight underline&lt;br /&gt;
|-&lt;br /&gt;
|6||highlight blink&lt;br /&gt;
|-&lt;br /&gt;
|7||highlight blink underline&lt;br /&gt;
|-&lt;br /&gt;
|8||reverse&lt;br /&gt;
|-&lt;br /&gt;
|9||reverse underline&lt;br /&gt;
|-&lt;br /&gt;
|A||reverse blink&lt;br /&gt;
|-&lt;br /&gt;
|B||reverse blink underline&lt;br /&gt;
|-&lt;br /&gt;
|C||reverse highlight&lt;br /&gt;
|-&lt;br /&gt;
|D||reverse highlight underline&lt;br /&gt;
|-&lt;br /&gt;
|E||reverse highlight blink&lt;br /&gt;
|-&lt;br /&gt;
|F||reverse highlight blink underline&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Once the PRINT HEX$(&amp;quot;Ex&amp;quot;) statement is executed, Business Rules uses the specified attribute for all screen output until a new PRINT HEX$(&amp;quot;Ex&amp;quot;) statement (or a PRINT NEWPAGE) is encountered.&amp;lt;br&amp;gt;&lt;br /&gt;
This method is not fully portable between equipment types -especially A to F.&lt;br /&gt;
&lt;br /&gt;
===Icon===&lt;br /&gt;
 SETENV(&amp;quot;ICON&amp;quot;,&amp;quot;myicon.ico&amp;quot;) sets the icon for the window and the taskbar to the icon specified by the second parameter (e.g. myicon).&lt;br /&gt;
&lt;br /&gt;
===Graphical Console===&lt;br /&gt;
&lt;br /&gt;
A new keyword, FONTS, has been added to the STATUS command that lists installed fonts that are suitable for use with BR.&lt;br /&gt;
&lt;br /&gt;
The OPEN #0: string can contain the keyword MAXIMIZE or NOMAXIMIZE which will override the user&#039;s INI settings.  If not specified, the &amp;quot;Save Font Size And Position&amp;quot; settings will govern.&lt;br /&gt;
&lt;br /&gt;
{\b COMBO BOX GRAPHIC AND HOT TEXT}&lt;br /&gt;
&lt;br /&gt;
PRINT FIELDS &amp;quot;row, col, C 10, Q,X02&amp;quot;:  &amp;quot;Box Caption&amp;quot; will place a graphic combo box down arrow graphic to the right of the field.  When the field is double clicked, a HEX 02 keyboard character is generated.  The X02 could also be a four digit hex value (X5044) or a function key number (14).&lt;br /&gt;
&lt;br /&gt;
==KEYBOARD CHANGES==&lt;br /&gt;
&lt;br /&gt;
The significance of certain keyboard characters has been revised:&lt;br /&gt;
* = changed&amp;lt;br&amp;gt;&lt;br /&gt;
^ = with control key pressed&lt;br /&gt;
&lt;br /&gt;
  ^A - break ( this was ^C )&amp;lt;br&amp;gt;&lt;br /&gt;
  ^B - page up (back)&amp;lt;br&amp;gt;&lt;br /&gt;
  ^C - copy to clipboard&amp;lt;br&amp;gt;&lt;br /&gt;
  ^D - delete&amp;lt;br&amp;gt;&lt;br /&gt;
  ^E - end field&amp;lt;br&amp;gt;&lt;br /&gt;
  ^F - page down (forward)&amp;lt;br&amp;gt;&lt;br /&gt;
  ^G - backtab&amp;lt;br&amp;gt;&lt;br /&gt;
  ^H - backspace&amp;lt;br&amp;gt;&lt;br /&gt;
  ^I - tab&amp;lt;br&amp;gt;&lt;br /&gt;
  ^J - next field&amp;lt;br&amp;gt;&lt;br /&gt;
  ^K - prior field&amp;lt;br&amp;gt;&lt;br /&gt;
  ^L - right arrow&amp;lt;br&amp;gt;&lt;br /&gt;
  ^M - enter&amp;lt;br&amp;gt;&lt;br /&gt;
  ^N - left arrow&amp;lt;br&amp;gt;&lt;br /&gt;
  ^O - field minus&amp;lt;br&amp;gt;&lt;br /&gt;
  ^P - print screen&amp;lt;br&amp;gt;&lt;br /&gt;
  ^Q - toggle insert mode&amp;lt;br&amp;gt;&lt;br /&gt;
  ^R - up field (rise)&amp;lt;br&amp;gt;&lt;br /&gt;
  ^S - toggle hold mode (stop)&amp;lt;br&amp;gt;&lt;br /&gt;
*  ^T - down field ( this once was ^V )&amp;lt;br&amp;gt;&lt;br /&gt;
  ^U - field plus&amp;lt;br&amp;gt;&lt;br /&gt;
  ^V - paste clipboard content&amp;lt;br&amp;gt;&lt;br /&gt;
  ^W - home&amp;lt;br&amp;gt;&lt;br /&gt;
  ^X - cut to clipboard&amp;lt;br&amp;gt;&lt;br /&gt;
*  ^Y - was REPAINT SCREEN in Unix - now HELP  (back where it was)&amp;lt;br&amp;gt;&lt;br /&gt;
    unchanged in Windows - still HELP&amp;lt;br&amp;gt;&lt;br /&gt;
  ^Z - restore field&amp;lt;br&amp;gt;&lt;br /&gt;
  ^[ - escape&amp;lt;br&amp;gt;&lt;br /&gt;
  ^\ - toggle translation of numerics to linedraw characters&amp;lt;br&amp;gt;&lt;br /&gt;
*  ^- - REPAINT SCREEN ( this once was ^X )&amp;lt;br&amp;gt;&lt;br /&gt;
    use the minus key on the top row of the keyboard&amp;lt;br&amp;gt;&lt;br /&gt;
    along with the control key&lt;br /&gt;
&lt;br /&gt;
INPUT SELECT now displays lines like PRINT FIELDS.&lt;br /&gt;
&lt;br /&gt;
==Label Alignment==&lt;br /&gt;
&lt;br /&gt;
[[Image:NEWC0001.jpg]]&lt;br /&gt;
&lt;br /&gt;
Labels (captions) displayed are analyzed for spacing.  If more than one blank is found or line draw or underline characters are encountered (denoting a text field), a new control is delineated, which means that the label is split in two or more pieces.&lt;br /&gt;
&lt;br /&gt;
When such individual controls (label segments) are delineated the last non-whitespace character of each subfield is checked for a colon.  If it is a colon the text is right justified within the subfield.  In other words, the colon right aligns the data where it would be if fixed width fonts were used.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: DELINEAT.BR&lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;1,1,C 10&amp;quot; : &amp;quot;one two&amp;quot;    ! treated as a single label (one space in between)&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;2,1,C 10&amp;quot; : &amp;quot;one   two&amp;quot;    ! treated as two separate labels (three spaces)&lt;br /&gt;
 00300 PRINT FIELDS &amp;quot;3,1,C 10&amp;quot; : &amp;quot;one:  two&amp;quot;    ! treated as two separate labels (two spaces)!:! label &amp;quot;one&amp;quot; will be right justified in a 4 position subfield&lt;br /&gt;
 00400 PRINT FIELDS &amp;quot;4,1,C 10&amp;quot; : &amp;quot;one   two:&amp;quot;    ! treated as two separate labels (three spaces)!:! label &amp;quot;two&amp;quot; will be right justified&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
&lt;br /&gt;
[[Image:NEWC0002.jpg]]&lt;br /&gt;
&lt;br /&gt;
In the output of line 200 in the above example, &amp;quot;two&amp;quot; is left aligned where it would be if a fixed font were used.&lt;br /&gt;
&lt;br /&gt;
Proportional font characters have varying widths.&lt;br /&gt;
&lt;br /&gt;
In the following example, when a proportional font is used, the word &amp;quot;row&amp;quot; looks longer than the word &amp;quot;big&amp;quot; and the word &amp;quot;bit&amp;quot;, but the words &amp;quot;column&amp;quot;, &amp;quot;object&amp;quot;, and &amp;quot;locate&amp;quot; will still line up with each other when printed to the graphical console.  This is because positioning and sizing of controls on the window are managed by simulating fixed character positions.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: PROPFONT.BR&lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;1,1,C 30&amp;quot;: &amp;quot;Row         Column&amp;quot;&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;2,1,C 30&amp;quot;: &amp;quot;Big         Object&amp;quot;&lt;br /&gt;
 00300 PRINT FIELDS &amp;quot;3,1,C 30&amp;quot;: &amp;quot;Bit         Locate&amp;quot;&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
&lt;br /&gt;
[[Image:NEWC0003.jpg]]&lt;br /&gt;
&lt;br /&gt;
If proportional fonts are turned off by executing CONFIG GUI OFF, then each letter will be exactly the same height and width as all other letters. The above example will then produce the following output:&lt;br /&gt;
&lt;br /&gt;
[[Image:NEWC0004.jpg]]&lt;br /&gt;
&lt;br /&gt;
If a CHR$(5) character is encountered with proportional fonts on, it is replaced by a blank and a control boundary is delineated.  Delineating a new control implies continuing output at the position where it would be if the window were using fixed fonts. In other words, using CHR$(5) simulates fixed character positions and makes label &amp;quot;iiii&amp;quot; and label &amp;quot;wwww&amp;quot; have the same width.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: CHR5.BR&lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;1,1,C 10&amp;quot;: &amp;quot;iiii wwww&amp;quot;&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;2,1,C 10&amp;quot;: &amp;quot;iiii&amp;quot;&amp;amp;CHR$(5)&amp;amp;&amp;quot;wwww&amp;quot;&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
[[Image:NEWC0005.jpg]]&lt;br /&gt;
&lt;br /&gt;
One of the disappointments in moving to a GUI presentation is that text selection via DATAHILITE overrides the ATTR value. ATTR still applies to the current field (where the cursor is) once the data is deselected. But the color of selected text is necessarily determined by the operating environment. If you want to override the color of the current field immediately upon entry, then you will have to turn DATAHILITE OFF, so the text will not be selected upon entry.  So, if the ATTR specification is to be honored, then the new GUI should be run with DATAHILITE OFF.&lt;br /&gt;
&lt;br /&gt;
The new GUI mode does not permit bleed through the way the old console does. That is, anything written to an underlying window is not seen until the overlaying window is removed. However, a capability called FORCE VISIBILITY (described in a separate section) overcomes this limitation for programs designed for the old BR console.&lt;br /&gt;
&lt;br /&gt;
If a text field is written to a window that already has an overlapping text field, then the text field, which it overlaps (the original text field on the screen) is automatically deleted.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: OVERLAP.BR&lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;1,1,C 5,,B20&amp;quot;: &amp;quot;ooooo&amp;quot;  ! this button will be automatically deleted&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;1,5,C 2,,B40&amp;quot;: &amp;quot;xx&amp;quot;    ! this button overlaps the first one and causes the first field to be deleted&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
[[Image:NEWC0006.jpg]]&lt;br /&gt;
&lt;br /&gt;
There are a couple of exceptions to this:&lt;br /&gt;
&lt;br /&gt;
:1.) Labels printed to a window using a proportional font can sometimes extend beyond the area that they would occupy as a fixed width font. When this happens, BR extends the control (text space) to the necessary length. If another label is subsequently displayed in this extended area, then it is positioned under the previous label extension.  If a non-label is printed in this extended area, it is positioned over the label extension.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: OVERLAP2.BR&lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;1,1,C 8&amp;quot; : &amp;quot;SSSSSSSS&amp;quot;     ! label&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;1,9,C 4&amp;quot;: &amp;quot;oooo&amp;quot;    ! this label is positioned under the previous label&lt;br /&gt;
 00300 PRINT&lt;br /&gt;
 FIELDS &amp;quot;2,1,C 8&amp;quot; : &amp;quot;SSSSSSSS&amp;quot;  ! label&lt;br /&gt;
 00400 PRINT FIELDS &amp;quot;2,9,C 4,,B5&amp;quot;: &amp;quot;oooo&amp;quot;     ! this button is positioned over the previous label&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
[[Image:NEWC0007.jpg]]&lt;br /&gt;
&lt;br /&gt;
:2.) If a control is displayed over the top of a label&#039;s fixed character space, the original label is truncated by the new control using fixed width font logic. Each resulting segment is handled independently, as if it were a separate field.&lt;br /&gt;
&lt;br /&gt;
The name of this example is: OVERLAP3.BR&lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;1,1,C 8&amp;quot;: &amp;quot;abcdefgh&amp;quot;    ! this label is 8 character long&lt;br /&gt;
 00200 INPUT FIELDS &amp;quot;1,1,C 4&amp;quot;: VAR$    ! this control is over the top of the previous label&#039;s fixed character space and causes the label to be separated into two pieces&lt;br /&gt;
&lt;br /&gt;
;Output:&lt;br /&gt;
[[Image:NEWC0008.jpg]]&lt;br /&gt;
&lt;br /&gt;
==New Features-==&lt;br /&gt;
&lt;br /&gt;
===Inactive Fields===&lt;br /&gt;
Normal windows behavior is to dim inactive fields.  This can be overridden with the [inactive] attribute, which refers to inactive graphical controls. The attribute names are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
e.g.     CONFIG ATTRIBUTE [inactive]/#112233:#445566&lt;br /&gt;
&lt;br /&gt;
===Menu improvements===&lt;br /&gt;
The way menus are displayed has been improved with respect to speed and appearance. The R (retain) menu flag is now honored.&lt;br /&gt;
&lt;br /&gt;
===Hot Controls - Focus===&lt;br /&gt;
Any control with an FKEY associated with it via the PRINT FIELDS statement will remain hot at all times, irrespective of whether it is participating in an INPUT FIELDS operation.&lt;br /&gt;
&lt;br /&gt;
[NEWCELL(icells3l.spc)]&lt;br /&gt;
===Paste behavior and Navigation Keys===&lt;br /&gt;
;Paste behavior&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot;&lt;br /&gt;
|Action&#039;&#039;&#039;||Old&#039;&#039;&#039;||New&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|Ctrl-V&#039;&#039;&#039;||paste goes into multiple fields at line endings&#039;&#039;&#039;||the current field gets the data it can take&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|left/right arrows &#039;&#039;&#039;||cause a field exit at beginning and end of field &#039;&#039;&#039;||arrow keys stop at end of field&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|up/down arrows&#039;&#039;&#039;||prior/next field&#039;&#039;&#039;||the same as left and right&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
;Navigation Keys&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot;&lt;br /&gt;
|Action&#039;&#039;&#039;||Old&#039;&#039;&#039;||New&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|up/down&#039;&#039;&#039;||up/down cell&#039;&#039;&#039;||up/down cell&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|left/right&#039;&#039;&#039;||left/right cell&#039;&#039;&#039;||left/right cell&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|Home/End in GRID&#039;&#039;&#039;||get focus &#039;&#039;&#039;||set cursor&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|left/right&#039;&#039;&#039;||cause a field exit at &#039;&#039;&#039;||&#039;&#039;&#039;||arrow keys stop at end of field&#039;&#039;&#039;||&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
===Data entry in a Grid===&lt;br /&gt;
&lt;br /&gt;
==INTERNAL PROCESSING CHANGES==&lt;br /&gt;
===WSID 0 No longer allowed===&lt;br /&gt;
&lt;br /&gt;
WSID 0 is no longer allowed. The priority of WSID assignment is:&lt;br /&gt;
:1.) Command line WSID requests take precedence over brconfig.sys WSID statements.&lt;br /&gt;
:2.) Of the brconfig.sys WSID statements, the last one specified is used.&lt;br /&gt;
&lt;br /&gt;
==NEW AND EXTENDED FEATURES==&lt;br /&gt;
&lt;br /&gt;
The I field attribute represents invisible. Under Windows it is now treated as a password field. Most typically it is displayed as a string of asterisks (e.g. *****).&lt;br /&gt;
&lt;br /&gt;
Long filenames may now be up to 512 characters in length.&lt;br /&gt;
&lt;br /&gt;
===Clipboard Access===&lt;br /&gt;
&lt;br /&gt;
{{:Clipboard}}&lt;br /&gt;
&lt;br /&gt;
==New Status Commands==&lt;br /&gt;
[NEWCELL(icells1l.spc)]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===STATUS and Partial Filename===&lt;br /&gt;
&lt;br /&gt;
BR now supports partial filenames on the STATUS FILES and STATUS LOCKS commands:&lt;br /&gt;
[NEWCELL(icells1l.spc)]&lt;br /&gt;
&lt;br /&gt;
==BUG FIXES==&lt;br /&gt;
The text portion of Combo Boxes can now be used as ordinary FIELDS as in:&lt;br /&gt;
&lt;br /&gt;
 01010 INPUT FIELDS &amp;quot;5,10, C 12; 7,10,COMBO 15; 9,10,C 8&amp;quot;: MAT DATA$&lt;br /&gt;
&lt;br /&gt;
Formerly, the statement had to read:&lt;br /&gt;
&lt;br /&gt;
 01010 INPUT FIELDS &amp;quot;5,10, C 12; 7,10,COMBO 15; 9,10,C 8&amp;quot;: DATA$(1),DATA$(2),DATA$(3)&lt;br /&gt;
&lt;br /&gt;
DIM statements were not being reinitialized prior to Save and Replace operations. This caused arrays to be corrupted when they were redimensioned in a library routine prior to replacing a program. This was an ancient bug.&lt;br /&gt;
&lt;br /&gt;
[[Category:Basics]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Rewrite&amp;diff=11501</id>
		<title>Rewrite</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Rewrite&amp;diff=11501"/>
		<updated>2026-02-23T03:01:38Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;ReWrite&#039;&#039;&#039; (REW) [[statement]] updates an existing record in an internal or external file.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
Before ReWrite can be used, an [[internal]] or [[external]] file must already be [[open]]ed and assigned a [[file number]]. ReWrite can be used with internal files opened for either [[SEQUENTIAL]],[[RELATIVE]] or [[KEYED]] processing. ReWrite can be used with external files opened for SEQUENTIAL or RELATIVE processing. ReWrite operates only when files are opened [[OUTIN]]. ReWrite must follow a successful [[READ]] or [[REREAD]] when used with a SEQUENTIAL file, when used without a [[REC=]] clause or [[POS=]] clause for RELATIVE files, and when used without a [[KEY]] clause for KEYED files. In each of these three cases, the record updated will be the last record read.&lt;br /&gt;
&lt;br /&gt;
The following example is an excerpt from a program which rewrites the changed values of a [[GRID]]. REWRITE functions like WRITE, except the record already exists:&lt;br /&gt;
&lt;br /&gt;
 00300  RECFORM: form C 30,C 30,C 30,C 15,C 2,C 7,C 1,N 1,N 1,N 1&lt;br /&gt;
     ...&lt;br /&gt;
 00880    for Index=1 to Udim(Mat Firstname$)&lt;br /&gt;
 00890           let Thesubscriptnumber=Subscr(Index)&lt;br /&gt;
 00900           let Therecordnumber=Recordnum(Thesubscriptnumber)&lt;br /&gt;
 00910           rewrite #1, using RECFORM, rec=Therecordnumber: Firstname$(Index), Lastname$(Index), Address$(Index), City$(Index), State$(Index), Zipcodes$(Index), Shipmethod$(Index), Item1(Index), Item2(Index), Item3(Index)&lt;br /&gt;
 00920     next Index&lt;br /&gt;
     ...&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 REWRITE #&amp;lt;[[file number]]&amp;gt; [, [[USING]] {&amp;lt;[[string expression]]&amp;gt;|&amp;lt;[[line ref]]&amp;gt;}] {[, [[Pos Parameter|POS]]=&amp;lt;[[numeric expression]]&amp;gt;]|[, [[REC=]]&amp;lt;[[numeric expression]]&amp;gt;]|[, [[KEY]]=&amp;lt;[[string expression]]&amp;gt;]} [, {[[RESERVE]]|[[RELEASE]]}] : [{[[Mat]] &amp;lt;[[array name]]&amp;gt;|&amp;lt;data item&amp;gt;}][,...]  [&amp;lt;[[error condition]]&amp;gt; &amp;lt;[[line ref]]&amp;gt;][,...]&lt;br /&gt;
[[Image:Rewrite.png|900px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Unformatted.&lt;br /&gt;
# Write over the last record read.&lt;br /&gt;
# Rewrite the current record and unlock all locked records for this file.&lt;br /&gt;
# Interrupt the program if an error occurs and &amp;quot;ON error&amp;quot; is not active.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&amp;quot;File-num&amp;quot; is an integer or numeric expression; it matches a REWRITE statement to a file of the same number that has already been identified in an OPEN statement.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;USING&amp;quot; keyword is part of a clause that specifies either the &amp;quot;line-ref&amp;quot; of a [[FORM]] statement or a &amp;quot;string-expr&amp;quot; containing a FORM statement.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;POS = num-expr&amp;quot; clause is used only with external files opened for RELATIVE processing; its purpose is to position to a specified byte before rewriting. After the numeric expression is evaluated and rounded to an integer, the system reads a complete record beginning at the specified byte number.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;REC = num-expr&amp;quot; parameter is used with internal files opened for RELATIVE or KEYED processing, or with external files opened for RELATIVE access; its purpose is to specify a record by record number. After the numeric expression (usually a simple numeric variable or constant) is evaluated and rounded to an integer, the system reads and then rewrites that record number.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;KEY = string-expr&amp;quot; clause is used with files opened for KEYED processing; its purpose is to specify a record by the value of its key field. After the string expression is evaluated, the system reads and then rewrites the first record that matches the key field. For complete information about the KEY clause, see the Index Facility chapter.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;RESERVE&amp;quot; and &amp;quot;RELEASE&amp;quot; parameters specify record locking rules for multi-user systems. &amp;quot;RESERVE&amp;quot; instructs the system to preserve all previous record locks. &amp;quot;RELEASE&amp;quot; releases all record locks on this file as soon as the REWRITE is complete.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;data-item&amp;quot; parameters represent a list of items to be written to the old record. If more than one is specified, they must be separated by commas.&lt;br /&gt;
&lt;br /&gt;
REWRITE allows error processing with the optional &amp;quot;error-cond line-ref&amp;quot; parameter. See [[Error Conditions]] for more information.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Relevant error conditions are: [[CONV]], [[ERROR]], [[EXIT]], [[IOERR]], [[LOCKED]], [[NOKEY]], [[NOREC]], and [[SOFLOW]].&lt;br /&gt;
# On multi-user systems, the record being rewritten must be locked or an error [[0062]] (record not locked) will result.&lt;br /&gt;
# If a &amp;quot;string-expr&amp;quot; is used with a USING clause, it must begin with &amp;quot;FORM &amp;quot;. This technique executes relatively slowly because the string is compiled on each execution of the REWRITE statement.&lt;br /&gt;
# Omitting the USING clause allows unformatted file I/O. See [[File I/O]] for more information.&lt;br /&gt;
# Unspecified positions in the record (e.g., skipped over by POS n or X n) remain unchanged.&lt;br /&gt;
# With RELATIVE processing, the REWRITE is not affected by the previous I/O statement when a REC= clause or POS= clause is used. An implied READ REC= is performed before the REWRITE.&lt;br /&gt;
# In a file opened for KEYED processing, it is possible to rewrite records by record number without disturbing the key position in the file. Using the record number for access is much faster than using a key. One caution is that you should not use REWRITE REC= if you have changed one or more key fields in a record. Since REWRITE REC= has no effect on the key files, a changed key field would prevent the key file from matching the master file.&lt;br /&gt;
# With KEYED processing, the REWRITE is not affected by the previous I/O statement when a KEY clause is used. An implied READ KEY= is performed before the REWRITE.&lt;br /&gt;
# &amp;quot;KEY=&amp;quot; is the only form allowed when REWRITE is used with a KEY clause. &amp;quot;KEY&amp;gt;=&amp;quot;, &amp;quot;SEARCH=&amp;quot;, and &amp;quot;SEARCH&amp;gt;=&amp;quot; matches are not allowed because their results (partial and &amp;quot;next greater&amp;quot; matches) are unpredictable.&lt;br /&gt;
# No error results when a REWRITE statement tries to change the key field. The fields used for other keys can also be changed when multiple index files are being maintained, and the index files will be updated automatically. For more information about multiple index files, see the Index Facility chapter.&lt;br /&gt;
# When OPTION INVP is in effect, the normal output of commas and periods is interchanged in PIC, N, NZ, G and GZ format specifications to produce European-style numbers. See the [[OPTION]] statement for details.&lt;br /&gt;
# When REWRITING REC=, or just REWRITE after a READ REC=, to a file with indexes open, the indexes will be maintained. Previously REC= processing ignored indexes on a rewrite. A consequence of this is the undetectability of multiple opens input noshr.  However, this poses no jeopardy to data.  Any open requesting write permissions will not be allowed while the file is opened input noshr, and no input noshr will be allowed while the file is opened for writing, but two opens input noshr MAY be allowed on the same file at the same time.  In other words in this case sharing is not blocked.&lt;br /&gt;
&lt;br /&gt;
  &lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
[[Category:File Processing Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Opening_a_Print_File&amp;diff=11500</id>
		<title>Opening a Print File</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Opening_a_Print_File&amp;diff=11500"/>
		<updated>2026-02-21T19:59:06Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A print file or printer direction is a DISPLAY FILE (see [[:Category:File Operations]]).  The default length for a record of a display file is 132 characters, this can be changed in the open statement to be as large as 32,000.  A record written to a display file by default is right trimmed and followed by a line ending sequence that is dependent upon the operating system.  For example, a Windows system will append a carriage return and a line feed.  The right trim of a display file record can be overridden by setting an option (see [[Option (Config)|OPTION]]) only if the EOL code is changed to NONE.&lt;br /&gt;
&lt;br /&gt;
Opening a display file with the number 255: uses the default BR printer configuration unless other parameters are specified in the open string.&lt;br /&gt;
&lt;br /&gt;
 00100 open #255:&amp;quot;name=PRN:WORD/\\\server1\\HP4,recl=32000,eol=NONE&amp;quot;, display,output&lt;br /&gt;
&lt;br /&gt;
The above line will open a printer that utilizes the BR SPOOLCMD functionality IF SPOOLCMD has been specified in the BRCONFIG.SYS file.  The string &amp;quot;WORD&amp;quot; will be passed to the spoolcommand function in the position of the PRINTQUEUE parameter, no end of line characters will be added at the end of each line, all lines will be trimmed of any space characters between the last non-space and the end of the line, no line will be wrapped unless it exceeds 32,000 characters (Note that because the EOL is set to NONE there will never be any effective line wrapping.)&lt;br /&gt;
&lt;br /&gt;
:1.) Opening a display file with {\b Name=WIN:/nn} does the same thing as PRN:/nn except the use of SPOOLCMD is suppressed only for that file and the print job is directed to the Windows print driver for the named printer.&lt;br /&gt;
&lt;br /&gt;
:2.) Opening a display file with {\b Name=DIRECT:/nn} does the same thing as PRN:/nn except the use of SPOOLCMD is suppressed only for that file and the print job is directed to port specified by the Windows print driver for the named printer without having the output processed by the print driver.&lt;br /&gt;
&lt;br /&gt;
3.) Opening a display file with {\b Name=PREVIEW:/nn} does the same thing as WIN:/nn except the print output is displayed as a preview after being processed by the printer driver, but before being sent to the named printer.  See [[NWP]] only print codes for acceptable escape sequences for WIN and PREVIEW (NWP) mode.&lt;br /&gt;
&lt;br /&gt;
If EOL is not set to NONE and it is desired that the print file, or display file, NOT be right trimmed then the file should not be opened as a display file, but rather as an EXTERNAL file (see [[:Category:File Operations]]).  If using an external file and EOL should be Carriage Return and Line Feed then the record length of the external file should be set a two characters longer than the desired record length and the last two positions of each line should be written with these characters (CHR$(13)&amp;amp;CHR$(10)).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Printing]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Field_Specs&amp;diff=11499</id>
		<title>Field Specs</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Field_Specs&amp;diff=11499"/>
		<updated>2026-02-19T05:54:47Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The following syntax may be inserted where the &amp;quot;field- spec&amp;quot; parameter appears in the main diagram for full screen processing statements, including [[INPUT FIELDS]], [[INPUT SELECT]], [[RINPUT FIELDS]], [[RINPUT SELECT]]. &lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 &amp;lt;row&amp;gt;, &amp;lt;column&amp;gt;, {&amp;lt;[[numeric form spec]]&amp;gt;|&amp;lt;[[string form spec]]&amp;gt;|&amp;lt;[[PIC]](&amp;lt;pic spec&amp;gt;}[&amp;lt;field length&amp;gt;] [.&amp;lt;fraction length&amp;gt;] [, &amp;lt;[[attributes]]&amp;gt;] [;...]&lt;br /&gt;
&lt;br /&gt;
[[file:Fieldspec.png|800px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
:1) Fraction length = 0.&lt;br /&gt;
:2) Maximum DIM length.&lt;br /&gt;
:3) Use current attributes.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
If the &amp;quot;string-expr&amp;quot; or &amp;quot;MAT string-array&amp;quot; parameters are used to specify the Field Spec definition, each must include the same elements identified below:&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;row&amp;quot; parameter is a required integer value that indicates the row number on the screen or in the window where the data is to be entered.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;column&amp;quot; parameter is a required integer value that indicates the starting column number on the screen where data is to be entered.&lt;br /&gt;
&lt;br /&gt;
As of 4.30, ROW/COL and ROWS/COLS in FIELDS operations may be expressed as decimal fractions.&lt;br /&gt;
&lt;br /&gt;
The next set of parameters specifies the format type of the data to be entered. There are three possible routes.&lt;br /&gt;
In the top route, &amp;quot;num form-spec&amp;quot; represents a numeric format specification. G, GL, GU, GZ, L, N, and NZ are all valid in this position; the specification must be followed by a space. The &amp;quot;field-length&amp;quot; parameter, which is an integer value that identifies the length (including decimal point and decimal positions) of the field, comes next. If the field contains decimal positions, &amp;quot;fraction&amp;quot; should identify how many there are; this parameter must be separated from the field length by a period (no spaces).&lt;br /&gt;
&lt;br /&gt;
In the middle route, &amp;quot;string form-spec&amp;quot; represents a string format specification. C, CC, CR, CL, CU, G, GL, GU, GZ, V, VL and VU are all valid in this position. If &amp;quot;field length&amp;quot; is used, it must be separated from the format spec by a space. As of 4.3 #G can be used to process numeric data from a string. &lt;br /&gt;
&lt;br /&gt;
In the bottom route, the &amp;quot;PIC&amp;quot; specification allows you to specify a picture of the field to be input. See [[PIC]] for a list of the characters that may be used in the &amp;quot;(pic-spec)&amp;quot; portion of this parameter. [[FMT]] can also be used in this place. &lt;br /&gt;
&lt;br /&gt;
A special string format specification called [[Picture|PICTURE]] &#039;&#039;rows/cols&#039;&#039; is also valid where string field specifications are permitted. &lt;br /&gt;
&lt;br /&gt;
In the place of PIC, #[[PIC]], #[[FMT]] or [[Text]] could be used. #PIC and #FMT allow numeric data from a string to be used, while Text forms a text box for input (as of 4.3).&lt;br /&gt;
&lt;br /&gt;
See [[Field specifications]] and [[Format specifications]] for additional information about all the format specifications listed in this section.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;attributes&amp;quot; parameter represents an insertable syntax that identifies the appearance and control attributes that are to be used for the field. See [[Attribute (Screen)]] for details. Additionally, fonts can be controlled at the field level by assigning them to attribute substitution letters as in:&lt;br /&gt;
&lt;br /&gt;
 CONFIG ATTRIBUTE [J],font=ariel:slant:max&lt;br /&gt;
&lt;br /&gt;
This specifies ariel italics maximum size.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Definitions]]&lt;br /&gt;
[[Category:All Parameters]]&lt;br /&gt;
[[Category:Field Specifications]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Days&amp;diff=11498</id>
		<title>Days</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Days&amp;diff=11498"/>
		<updated>2026-02-18T17:16:53Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt; DAYS (&amp;lt;date&amp;gt;[,&amp;lt;format$&amp;gt;])&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DAYS&#039;&#039;&#039; [[internal function]] returns the absolute, sequential value which is assigned to dates beginning January 1, 1900.&lt;br /&gt;
&lt;br /&gt;
BaseYear is used to determine the century for a date if the format specification used does not specify a century. See [[BaseYear]] in the [[BRConfig.sys]] specifications section for complete information.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
The Days function returns the specified date as a sequential value in relation to a base date of January 1, 1900. As an example of using this parameter for date arithmetic, line 300 would print &amp;quot;PAST DUE&amp;quot; if the date of an invoice &#039;&#039;&#039;IDATE&#039;&#039;&#039; is over 30 days from the current date.&lt;br /&gt;
&lt;br /&gt;
 00030 IF Days(DATE)&amp;gt;Days(IDATE)+30 THEN PRINT &amp;quot;Past Due&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The DAYS function can now be used to store and perform date arithmetic on dates beginning with year 1700.  Negative numbers are used to denote such dates.&lt;br /&gt;
&lt;br /&gt;
Notice that the number of days in a month, leap year, etc. do not have to be coded in your program because they are built into this function.&lt;br /&gt;
&lt;br /&gt;
If the numeric date parameter is invalid, the Days function will return zero. Therefore, the Days function can also be used to check the validity of dates entered from the keyboard. For example,&lt;br /&gt;
&lt;br /&gt;
 00010 LET DATE$(&amp;quot;*y/m/d&amp;quot;) ! be sure default format is year/month/day&lt;br /&gt;
 00020 LET M$=&amp;quot;14&amp;quot;         ! month is invalid&lt;br /&gt;
 00030 LET D$=&amp;quot;31&amp;quot;&lt;br /&gt;
 00040 LET Y$=&amp;quot;88&amp;quot;&lt;br /&gt;
 00050 LET D=VAL(Y$&amp;amp;M$&amp;amp;D$) ! D = 881431&lt;br /&gt;
 00060 PRINT Days(D)       ! invalid date = 0&lt;br /&gt;
 00070 LET M$=&amp;quot;12&amp;quot;         ! month is valid&lt;br /&gt;
 00080 LET D=VAL(Y$&amp;amp;M$&amp;amp;D$) ! D = 881231&lt;br /&gt;
 00090 PRINT Days(D)       ! valid date = 32507&lt;br /&gt;
 00095 LET DATE$(&amp;quot;*m/d/y&amp;quot;) ! change default format to month/day/year&lt;br /&gt;
 00096 PRINT Days(D)       ! invalid date = 0 (does not fit format)&lt;br /&gt;
&lt;br /&gt;
The value of D in line 96 is invalid because line 95 changes the default format for dates (note the * at the start of the date string). The optional second parameter of the Days function can be used to temporarily change the date format. Line 97 will print a nonzero value because the date is valid in the format specified in the optional string parameter.&lt;br /&gt;
&lt;br /&gt;
 00097 PRINT Days(D,&amp;quot;y/m/d&amp;quot;) ! valid date = 32507&lt;br /&gt;
 00098 PRINT Days(D)         ! invalid date = 0 (does not fit format specified  in line 95)&lt;br /&gt;
&lt;br /&gt;
Line 98 returns zero because the format in line 97 only applies to that one function call. Since there is no asterisk in the date string, line 97 does not change the default date format, whereas line 95 does.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The &amp;quot;date&amp;quot; parameter is a numeric expression that represents the date for which the number of days should be calculated. If &amp;quot;date&amp;quot; is not valid according to the current default format, Days will return 0.&lt;br /&gt;
&lt;br /&gt;
The optional &amp;quot;format$&amp;quot; parameter is a string expression which identifies the format of the value to be returned. When the first character of the string expression includes an asterisk (*), it identifies the default format which should be used by the Date, Date$ and Days parameters until the workstation exits Business Rules or until the format is changed again. Format changes affect the current workstation only.&lt;br /&gt;
&lt;br /&gt;
The format$ parameter may include separating characters and any of the following date specifications: D (day), M (month), Y (year) or C (century). The total number of separating characters and date specifications may not exceed 6. Consecutive repetitions (DDD, YY, etc.) of the date specifications count as just one specification, but consecutive repetitions of separating characters do not use this rule. See the Date$ function for additional information about format$.&lt;br /&gt;
&lt;br /&gt;
===Handling Input===&lt;br /&gt;
(4.2+) BR does not require that the entered value conform to the specified mask to avoid the entry of incorrect dates using unforeseen valid expressions. If a date format mask omits month, day, year and/or century, BR assumes the first day of the current month for the respective omitted mask components. Values must conform to masks with the following exceptions:&lt;br /&gt;
&lt;br /&gt;
* Any valid format for month or day is accepted on input where the mask requests the respective month or day.&lt;br /&gt;
* Any valid separator will be accepted where any separator is specified by the mask.&lt;br /&gt;
&lt;br /&gt;
Otherwise, BR requires that the entered value conform to the specified mask to &lt;br /&gt;
avoid the entry of incorrect dates using unforeseen valid expressions.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
   days(&amp;quot;Tuesday 23 January, 2007&amp;quot;,&#039;day dd month, ccyy&#039;) -&amp;gt; 39104&lt;br /&gt;
   days(&amp;quot;January, 2007&amp;quot;,&#039;day dd month, ccyy&#039;) -&amp;gt; 39082  (day 1 assumed)&lt;br /&gt;
   days(&amp;quot;Tuesday 23 Jan; 07&amp;quot;,&#039;day dd m3, yy&#039;) -&amp;gt; 39104  (note mm yy separator)&lt;br /&gt;
   days(&amp;quot;Tuesday 23 Jan/ 07&amp;quot;,&#039;day dd m3, yy&#039;) -&amp;gt; 39104&lt;br /&gt;
   days(&amp;quot;Tuesday 23 Jan; 07&amp;quot;,&#039;day dd m3, yy&#039;) -&amp;gt; 39104&lt;br /&gt;
   days(&amp;quot;Tuesday 23 Jan- 07&amp;quot;,&#039;day dd m3, yy&#039;) -&amp;gt; 39104&lt;br /&gt;
   days(&amp;quot;23 Jan;&amp;quot;,&#039;dd m3,&#039;)                   -&amp;gt; 40200  (current year and century)&lt;br /&gt;
&lt;br /&gt;
BR allows a null mask, but the omission of a mask does not denote a null mask. It denotes whatever the system default mask is currently set to, which could be the system default.&lt;br /&gt;
&lt;br /&gt;
*As of 4.3, numeric variables can now represent time of day in the fractional space (to the right of the decimal point) to include the following:&lt;br /&gt;
   H#.## or H denotes hours (with fractions).&lt;br /&gt;
   M#.### or M#.# denotes minutes.&lt;br /&gt;
   S#.####, S or S# denotes seconds.&lt;br /&gt;
M, to the right of H always denotes minutes, so H:M:S is sufficient.&lt;br /&gt;
Either AM or PM, to the right of H denotes AM / PM output.&lt;br /&gt;
The absence of AM and PM denote military hours (0 – 23).&lt;br /&gt;
The maximum significant digits that can be represented in a numeric variable are 15. So if century, year, month and day are stored as a 5 digit day of century, then internally up to ten digits to the right of the decimal are available for time of day.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
   DAYS(&amp;quot;7/13/15 03:10:57 AM&amp;quot;,&amp;quot;M/D/Y H:M:S AM&amp;quot;)     -&amp;gt; 42197.1326042&lt;br /&gt;
   DAYS(&amp;quot;03:10:57 AM&amp;quot;,&amp;quot;H:M:S AM&amp;quot;)                   -&amp;gt; 42185.1326042   &lt;br /&gt;
   DAYS(&amp;quot;7/13/15 03.1825&amp;quot;,&amp;quot;M/D/Y H#.####&amp;quot;)       -&amp;gt; 42197.1326042&lt;br /&gt;
Notes: &lt;br /&gt;
* When using H:M:S, the time Component must be HH:MM:SS, so for example 3:10:57 PM will not work properly &lt;br /&gt;
* When providing a time without a date, the function will return the days value for the 1st day of THIS month. In the Above examples, the date was 7/13/15&lt;br /&gt;
* When using H#.#### the Hours must be 2 Digits &amp;quot;03&amp;quot; not &amp;quot;3&amp;quot;, and the # of decimals must at least match the mask provided.&lt;br /&gt;
&lt;br /&gt;
===Saving Dates===&lt;br /&gt;
When storing date/time combinations in a data file, you should allow for all of the significant digits that your date mask supports on each side of the decimal point. A “BH 4.4” form supports nine significant digits, which is suitable for day of century plus a four digit fraction. To exceed that you can use either PD 6.6, PD 7.6, PD 8.6 or D 8 (double floating point) to store these values. Note that BR rounds internally at six digits by default.&lt;br /&gt;
&lt;br /&gt;
===Related Functions===&lt;br /&gt;
&lt;br /&gt;
See also [[Date$]] and [[Date]] for other date processing functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Internal Functions]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Insert_(config)&amp;diff=11494</id>
		<title>Insert (config)</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Insert_(config)&amp;diff=11494"/>
		<updated>2025-09-15T04:39:45Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Insert&#039;&#039;&#039; [[BRConfig.sys]] specification:&lt;br /&gt;
&lt;br /&gt;
When the cursor enters a field the &#039;&#039;overtype/insert&#039;&#039; setting can be controlled by the INSERT statement. The syntax of this statement is:&lt;br /&gt;
&lt;br /&gt;
 CONFIG INSERT ON | OFF  [MIN_LENGTH &amp;lt;0-99&amp;gt;]|[NON_PERSISTENT]|[SESSION_PERSISTENT]|[PERSISTENT] &lt;br /&gt;
&lt;br /&gt;
====INSERT Configuration Statement Parameters====&lt;br /&gt;
INSERT ON | OFF - sets mode to Insert or Overtype &amp;lt;br&amp;gt;&lt;br /&gt;
INSERT MIN_LENGTH 10 - regulates separate long and short insert settings. MIN_LENGTH denotes that INSERT should be ON if the field capacity equals or exceeds the specified numeric value.&amp;lt;br&amp;gt;&lt;br /&gt;
INSERT [ON|OFF] NON_PERSISTENT - resets to specified setting upon field exit &amp;lt;br&amp;gt;&lt;br /&gt;
INSERT [ON|OFF] SESSION_PERSISTENT - resets at the beginning of each BR invocation &amp;lt;br&amp;gt;&lt;br /&gt;
INSERT [ON|OFF] PERSISTENT - user setting persists across BR invocations &amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The default mode is ON PERSISTENT.&amp;lt;br&amp;gt;&lt;br /&gt;
HOWEVER the default setting action is overridden by the [[DataHilite]] configuration statement action. Datahilite causes the content of a field to be hilighted and if the user just starts typing it clears the field before displaying the typed value. &lt;br /&gt;
&lt;br /&gt;
This statement can also be set using the [[Config]] command, or from within the program using the [[execute]] statement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Config]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Delete_(command)&amp;diff=11477</id>
		<title>Delete (command)</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Delete_(command)&amp;diff=11477"/>
		<updated>2024-11-19T16:51:45Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Del (DE)&#039;&#039;&#039; [[command]] deletes one or more lines from the program currently loaded in memory.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
At least one line number must follow the DEL command. The system will delete the specified line or, if two line numbers are indicated, it will delete both lines and all lines in between.&lt;br /&gt;
&lt;br /&gt;
The following example deletes lines 30-70:&lt;br /&gt;
&lt;br /&gt;
 DEL 30 70&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 DEL &amp;lt;first line number&amp;gt; [&amp;lt;last line number&amp;gt;] [SOURCE]&lt;br /&gt;
[[Image:Del.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
:1) Delete only the specified line.&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
DEL requires the &#039;&#039;&#039;1st line number&#039;&#039;&#039; parameter, which is either the single line number you wish to delete, or the first line number of a range of lines you wish to delete.&lt;br /&gt;
&lt;br /&gt;
To delete a range of lines you must also use the &#039;&#039;&#039;last line number&#039;&#039;&#039; parameter. This is the number of the last line you wish to delete.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The following parameter is useful for enforcing security:&lt;br /&gt;
&lt;br /&gt;
SOURCE allows you to delete source code without deleting the object (executable) code. This lets you hide sections of your program for security purposes. However, once this is done the program cannot be reloaded from source because the source is misssing. So always keep a separate copy of the full original source.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
To open up all the storage space that deleted lines take up, save the file in source and reload it. This allows you to reclaim approximately 100 bytes of object code for every seven deleted lines.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Editing Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Delete_(command)&amp;diff=11476</id>
		<title>Delete (command)</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Delete_(command)&amp;diff=11476"/>
		<updated>2024-11-19T16:39:43Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Syntax */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Del (DE)&#039;&#039;&#039; [[command]] deletes one or more lines from the program currently loaded in memory.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
At least one line number must follow the DEL command. The system will delete the specified line or, if two line numbers are indicated, it will delete both lines and all lines in between.&lt;br /&gt;
&lt;br /&gt;
The following example deletes lines 30-70:&lt;br /&gt;
&lt;br /&gt;
 DEL 30 70&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 DEL &amp;lt;first line number&amp;gt; [&amp;lt;last line number&amp;gt;] [SOURCE]&lt;br /&gt;
[[Image:Del.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
:1) Delete only the specified line.&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
DEL requires the &#039;&#039;&#039;1st line number&#039;&#039;&#039; parameter, which is either the single line number you wish to delete, or the first line number of a range of lines you wish to delete.&lt;br /&gt;
&lt;br /&gt;
To delete a range of lines you must also use the &#039;&#039;&#039;last line number&#039;&#039;&#039; parameter. This is the number of the last line you wish to delete.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
To open up all the storage space that deleted lines take up, save the file in source and reload it. This allows you to reclaim approximately 100 bytes of object code for every seven deleted lines.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Editing Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Do&amp;diff=11475</id>
		<title>Do</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Do&amp;diff=11475"/>
		<updated>2024-10-06T11:29:21Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;See also [[DO LOOP]] and [[Exit Do]].&lt;br /&gt;
&lt;br /&gt;
The DO/LOOP structure, which can be used to replace [[GOTO]] statements for more structured programming. Notably, labels or line numbers are not required to exit the loop. The DO and LOOP statements must always be used in conjunction with one another to specify the beginning and ending of the loop. The Exit DO statement may be used to break out of the loop.&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 DO [{WHILE|UNTIL}  &amp;lt;[[conditional expression]]&amp;gt;]&lt;br /&gt;
[[Image:Do.png]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# When no WHILE or UNTIL condition is specified, execute.&lt;br /&gt;
# Loop until the corresponding LOOP statement&#039;s conditions are met, or an EXIT DO statement is encountered.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The WHILE keyword indicates that the loop should be executed only if the  specified conditional expression evaluates to true. If the conditional expression evaluates to false, execution will skip to the first line following the LOOP statement.&lt;br /&gt;
&lt;br /&gt;
The UNTIL keyword indicates that the loop should be executed only if the specified conditional expression evaluates to false. If the conditional expression evaluates to true, execution will skip to the first line following the LOOP statement.&lt;br /&gt;
&lt;br /&gt;
CONFIG STYLE INDENT will cause lines between the DO and LOOP statements to be indented.&lt;br /&gt;
&lt;br /&gt;
===Note===&lt;br /&gt;
I you need to execute a group of stements once irrespective of the loop terminating condition, don&#039;t specify a condition on the DO statement and instead place it on the LOOP statement.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
This do loop waits for [[Check Box]]es to be selected before the operator pushes a &amp;quot;Done&amp;quot; [[Print Fields Buttons|Button]]. (The button sets the Fkey to 99, thereby exiting the loop. &lt;br /&gt;
&lt;br /&gt;
 00350     do&lt;br /&gt;
 00360        input fields &amp;quot;22,14,CHECK 8,,10;23,14,CHECK 8,,11;24,14,CHECK 8,,12&amp;quot;: Box$(1),Box$(2),Box$(3)&lt;br /&gt;
 00370        print fields &amp;quot;24,1,N 2&amp;quot;: Fkey&lt;br /&gt;
 00380     loop While Fkey~=99&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
[[Category:Loop Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Do&amp;diff=11474</id>
		<title>Do</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Do&amp;diff=11474"/>
		<updated>2024-10-06T11:22:01Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;See also [[DO LOOP]] and [[Exit Do]].&lt;br /&gt;
&lt;br /&gt;
The DO/LOOP structure, which can be used to replace [[GOTO]] statements for more structured programming. Notably, labels or line numbers are not required to exit the loop. The DO and LOOP statements must always be used in conjunction with one another to specify the beginning and ending of the loop. The Exit DO statement may be used to break out of the loop.&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 DO [{WHILE|UNTIL}  &amp;lt;[[conditional expression]]&amp;gt;]&lt;br /&gt;
[[Image:Do.png]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# When no WHILE or UNTIL condition is specified, execute.&lt;br /&gt;
# Loop until the corresponding LOOP statement&#039;s conditions are met, or an EXIT DO statement is encountered.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The WHILE keyword indicates that the loop should be executed only if the  specified conditional expression evaluates to true. If the conditional expression evaluates to false, execution will skip to the first line following the LOOP statement.&lt;br /&gt;
&lt;br /&gt;
The UNTIL keyword indicates that the loop should be executed only if the specified conditional expression evaluates to false. If the conditional expression evaluates to true, execution will skip to the first line following the LOOP statement.&lt;br /&gt;
&lt;br /&gt;
CONFIG STYLE INDENT will cause lines between the DO and LOOP statements to be indented.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
This do loop waits for [[Check Box]]es to be selected before the operator pushes a &amp;quot;Done&amp;quot; [[Print Fields Buttons|Button]]. (The button sets the Fkey to 99, thereby exiting the loop. &lt;br /&gt;
&lt;br /&gt;
 00350     do&lt;br /&gt;
 00360        input fields &amp;quot;22,14,CHECK 8,,10;23,14,CHECK 8,,11;24,14,CHECK 8,,12&amp;quot;: Box$(1),Box$(2),Box$(3)&lt;br /&gt;
 00370        print fields &amp;quot;24,1,N 2&amp;quot;: Fkey&lt;br /&gt;
 00380     loop While Fkey~=99&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
[[Category:Loop Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Do&amp;diff=11473</id>
		<title>Do</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Do&amp;diff=11473"/>
		<updated>2024-10-06T11:14:42Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Defaults */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;See also [[DO LOOP]] and [[Exit Do]].&lt;br /&gt;
&lt;br /&gt;
The DO/LOOP structure, which can be used to replace [[GOTO]] statements for more structured programming. Notably, labels or line numbers are not required to exit the loop. The DO and LOOP statements must always be used in conjunction with one another to specify the beginning and ending of the loop. The Exit DO statement may be used to break out of the loop.&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 DO [{WHILE|UNTIL}  &amp;lt;[[conditional expression]]&amp;gt;]&lt;br /&gt;
[[Image:Do.png]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# When no WHILE or UNTIL condition is specified, execute.&lt;br /&gt;
# Loop until the corresponding LOOP statement&#039;s conditions are met, or an EXIT DO statement is encountered.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The WHILE keyword indicates that the loop should be executed only if the  specified conditional expression evaluates to true. If the conditional expression evaluates to false, execution will skip to the first line following the LOOP statement.&lt;br /&gt;
&lt;br /&gt;
The UNTIL keyword indicates that the loop should be executed only if the specified conditional expression evaluates to false. If the conditional expression evaluates to true, execution will skip to the first line following the LOOP statement.&lt;br /&gt;
&lt;br /&gt;
The word DO on the DO statement is optional when WHILE or UNTIL is specified. Business Rules will insert the word DO. CONFIG STYLE INDENT will cause lines between the DO and LOOP statements to be indented. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
This do loop waits for [[Check Box]]es to be selected before the operator pushes a &amp;quot;Done&amp;quot; [[Print Fields Buttons|Button]]. (The button sets the Fkey to 99, thereby exiting the loop. &lt;br /&gt;
&lt;br /&gt;
 00350     do&lt;br /&gt;
 00360        input fields &amp;quot;22,14,CHECK 8,,10;23,14,CHECK 8,,11;24,14,CHECK 8,,12&amp;quot;: Box$(1),Box$(2),Box$(3)&lt;br /&gt;
 00370        print fields &amp;quot;24,1,N 2&amp;quot;: Fkey&lt;br /&gt;
 00380     loop While Fkey~=99&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
[[Category:Loop Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Copy&amp;diff=11472</id>
		<title>Copy</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Copy&amp;diff=11472"/>
		<updated>2024-09-28T17:29:30Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Copy (COP)&#039;&#039;&#039; [[command]] copies one or more files in numerous ways:&lt;br /&gt;
# From one directory to another on the same or different disks.&lt;br /&gt;
# From one place on a directory to another place on the same directory. In this case you must give the resultant copy a different name.&lt;br /&gt;
# From a file to a printer.&lt;br /&gt;
# The -S option has been added to allow copying of a file which is currently open. The file to be copied must be opened for sharing ([[Shr]] or [[ShrI]]). If it is opened [[NoShr]], a file sharing violation error [[4148]] will occur when Copy with -S is attempted.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
When using the copy command, you may specify a drive letter or number, and a path. You may also use [[wildcard characters]] &#039;&#039;&#039;?&#039;&#039;&#039; and &#039;&#039;&#039;*&#039;&#039;&#039; in place of alphanumeric characters in file names. Redirection symbols &#039;&#039;&#039;&amp;gt;&#039;&#039;&#039; and &#039;&#039;&#039;&amp;gt;&amp;gt;&#039;&#039;&#039; redirect screen output to a printer, a file, or another device.&lt;br /&gt;
&lt;br /&gt;
The following command copies the file SAMPLE from the default directory of a disk in drive A to the default directory of a disk in drive B. The file name remains the same:&lt;br /&gt;
&lt;br /&gt;
 COPY A:SAMPLE B:&lt;br /&gt;
&lt;br /&gt;
The next command copies the file SAMPLE from the subdirectory \TEST1 on a disk in drive A to the subdirectory \TEST2 on the same disk. If TEST2 does not exist as a subdirectory, Business Rules names the file TEST2 and places it in the default directory on the default drive:&lt;br /&gt;
&lt;br /&gt;
 COPY A:\TEST1\SAMPLE A:\TEST2&lt;br /&gt;
&lt;br /&gt;
The following command copies SAMPLE from one place in the subdirectory \TEST1 on a disk in drive A to another place in the same subdirectory. The copied file is renamed SAMPLE1:&lt;br /&gt;
&lt;br /&gt;
 COPY A:\TEST1\SAMPLE A:\TEST1\SAMPLE1&lt;br /&gt;
&lt;br /&gt;
The next command copies the file SAMPLE from the default directory of a disk in drive B to a printer attached to the first parallel port:&lt;br /&gt;
&lt;br /&gt;
 COPY B:SAMPLE PRN:&lt;br /&gt;
&lt;br /&gt;
The same file can be copied to the printer at the first serial port with the following command:&lt;br /&gt;
&lt;br /&gt;
 COPY B:SAMPLE AUX:&lt;br /&gt;
&lt;br /&gt;
The next command copies all files with names starting with SAMPLE, a period and any extension from a disk in drive A to a disk in drive B. The &#039;&#039;&#039;D&#039;&#039;&#039; in the command causes deleted records to be removed from any of the SAMPLE files which are internal files. The &#039;&#039;&#039;C&#039;&#039;&#039; in the command causes Business Rules! to prompt for a diskette change if the disk in drive B fills up before the copying process is completed.&lt;br /&gt;
&lt;br /&gt;
 COPY A:SAMPLE.* B: -DC&lt;br /&gt;
&lt;br /&gt;
The following example copies the internal file SAMPLE from a disk in drive A to a disk in drive B, setting the record length of the new file at 128:&lt;br /&gt;
&lt;br /&gt;
 COPY A:SAMPLE B: -128&lt;br /&gt;
&lt;br /&gt;
As of 4.2, COPY supports copying a print file unaltered to a printer:&lt;br /&gt;
 COPY PRT\EDITLIST.255  DIRECT:/HP4&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 COPY &amp;lt;from file&amp;gt; {[AUX:]|[PRN:]|[&amp;lt;to file&amp;gt;]} {[&amp;gt;[&amp;gt;]&amp;lt;output file&amp;gt;]|[PRINT]} [&amp;lt;[[copy option|-option]]&amp;gt;][-...]&lt;br /&gt;
&lt;br /&gt;
[[Image:Copy.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
# Copy the file, using the current file name, into the current directory on the current drive.&lt;br /&gt;
# Display a log of all performed actions in the [[command console]].&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
&lt;br /&gt;
Copy requires the &#039;&#039;&#039;from file&#039;&#039;&#039; parameter, which provides the information necessary for identifying the file or files you wish to copy.&lt;br /&gt;
&lt;br /&gt;
After the &#039;&#039;&#039;from file&#039;&#039;&#039;, you may specify the optional parameter &#039;&#039;&#039;to file&#039;&#039;&#039;, which is the name (and path, if required) of the file you are creating.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PRN:&#039;&#039;&#039; allows you to copy the file indicated in the &#039;&#039;&#039;from file&#039;&#039;&#039; to the printer attached to the first parallel port on DOS systems. This string must be translated with the [[SUBSTITUTE]] specification in [[BRConfig.sys]] to specify the appropriate output device on [[Linux]] systems.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;AUX:&#039;&#039;&#039; copies the &#039;&#039;&#039;from file&#039;&#039;&#039; file to the printer attached to the first [[serial port]]. This string must be translated with the SUBSTITUTE specification for Business Rules! Linux versions.&lt;br /&gt;
&lt;br /&gt;
In place of the &#039;&#039;&#039;PRN:&#039;&#039;&#039; or &#039;&#039;&#039;AUX:&#039;&#039;&#039; parameters, you may also specify COM1:, COM2:, LPT1:, LPT2:, etc. to indicate the port to which your printer is attached (see the drive discussion in the Definitions chapter for more information). These device references will operate properly only on Business Rules DOS versions. They may be mapped to new references with the BRConfig.sys file&#039;s SUBSTITUTE specification.&lt;br /&gt;
&lt;br /&gt;
The redirection symbol in the &#039;&#039;&#039;&amp;gt;file-ref&#039;&#039;&#039; parameter saves messages that normally appear on the screen to the specified file. The &#039;&#039;&#039;PRINT&#039;&#039;&#039; parameter sends these same messages to the printer rather than to the screen or a file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Option&#039;&#039;&#039; may be one or more of several optional parameters. Such option(s) can follow the copy command when separated from the rest of the command with a dash:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-A&#039;&#039;&#039;||tells the system to copy only files that have the archive bit on. The system turns the archive bits in the original files off when the copying process is complete. This option applies to Business Rules! [[DOS]] versions only.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-C&#039;&#039;&#039;||prompts for a diskette change if the disk is full.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-D&#039;&#039;&#039;||omits deleted records from the copy, but responds only when used with internal files. It is important to rebuild all related index files after using this option.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-N&#039;&#039;&#039;||prevents the screen from showing a log of performed actions.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-P&#039;&#039;&#039;||causes scrolling to pause after a full screen of information has been displayed. (Press &amp;lt;CR&amp;gt; for next screen.)&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-S&#039;&#039;&#039;||option has been added to allow copying of a file which is currently open. The file to be copied must be opened for sharing (SHR or SHRI) if it is opened NOSHR, error [[4148]] (file sharing violation) will occur when COPY with the -S is attempted.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-V&#039;&#039;&#039;||requires the operator to Verify each action before Business Rules! performs it.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-#&#039;&#039;&#039;||represents output file record length. Internal file record lengths are shortened or expanded (with blanks) to match the number you indicate. &lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
Prior to version [[3.0]] the -S switch meant preserve space.  That syntax is no longer supported. -s now refers to file sharing as noted above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
COPY to a printer :DEVICE was being rejected. COPY to a PRN: or WIN: device is still illegal. (Use TYPE with redirection for PRN: and WIN:)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
While the [Source] Directory is very flexible supporting *, *.* &amp;amp; *.*.* to include &amp;quot;All Files&amp;quot;, the [Destination] Directory is less flexibe.    &lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination&lt;br /&gt;
* This sample will copy all files to Destination - This is the preferred syntax for copy&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination\*&lt;br /&gt;
* This sample will copy all files to Destination but will Remove any Extension so &amp;quot;Sample.wb&amp;quot; will become &amp;quot;Sample&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination\*.*&lt;br /&gt;
* This sample will copy all files to Destination but complex files like &amp;quot;Sample.wb.brs&amp;quot; will become &amp;quot;Sample.wb&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination\*.*.*&lt;br /&gt;
* This sample will copy all files to Destination complex files like &amp;quot;Sample.wb.brs&amp;quot; will become &amp;quot;Sample.wb.brs&amp;quot;  - Files with more &amp;quot;.&amp;quot; would also be truncated.&lt;br /&gt;
&lt;br /&gt;
==Client Server==&lt;br /&gt;
&lt;br /&gt;
 COPY    filename      @:filename      (@ indicates Client system)&lt;br /&gt;
&lt;br /&gt;
Copies filename TO client current directory&lt;br /&gt;
&lt;br /&gt;
(Or specify @:pathname relative to client current directory.)&lt;br /&gt;
&lt;br /&gt;
 COPY      @:filename      filename&lt;br /&gt;
&lt;br /&gt;
Copies filename FROM client current directory.&lt;br /&gt;
&lt;br /&gt;
If you are trying to use [[absolute paths]] on the [[client]], specify @::&lt;br /&gt;
&lt;br /&gt;
This is really most useful with [[OPEN:]] and [[SAVE:]] file selection browsers.&lt;br /&gt;
&lt;br /&gt;
Use caution when using absolute paths on the client because [[permissions]] and [[file systems]] can be different from one computer to another.&lt;br /&gt;
&lt;br /&gt;
Normally BR will use the [[startup directory]] to copy things and you can change this in the shortcut if needed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:File Management Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Copy&amp;diff=11471</id>
		<title>Copy</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Copy&amp;diff=11471"/>
		<updated>2024-09-28T17:27:50Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Comments and Examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Copy (COP)&#039;&#039;&#039; [[command]] copies one or more files in numerous ways:&lt;br /&gt;
# From one directory to another on the same or different disks.&lt;br /&gt;
# From one place on a directory to another place on the same directory. In this case you must give the resultant copy a different name.&lt;br /&gt;
# From a file to a printer.&lt;br /&gt;
# The -S option has been added to allow copying of a file which is currently open. The file to be copied must be opened for sharing ([[Shr]] or [[ShrI]]). If it is opened [[NoShr]], a file sharing violation error [[4148]] will occur when Copy with -S is attempted.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
When using the copy command, you may specify a drive letter or number, and a path. You may also use [[wildcard characters]] &#039;&#039;&#039;?&#039;&#039;&#039; and &#039;&#039;&#039;*&#039;&#039;&#039; in place of alphanumeric characters in file names. Redirection symbols &#039;&#039;&#039;&amp;gt;&#039;&#039;&#039; and &#039;&#039;&#039;&amp;gt;&amp;gt;&#039;&#039;&#039; redirect screen output to a printer, a file, or another device.&lt;br /&gt;
&lt;br /&gt;
The following command copies the file SAMPLE from the default directory of a disk in drive A to the default directory of a disk in drive B. The file name remains the same:&lt;br /&gt;
&lt;br /&gt;
 COPY A:SAMPLE B:&lt;br /&gt;
&lt;br /&gt;
The next command copies the file SAMPLE from the subdirectory \TEST1 on a disk in drive A to the subdirectory \TEST2 on the same disk. If TEST2 does not exist as a subdirectory, Business Rules names the file TEST2 and places it in the default directory on the default drive:&lt;br /&gt;
&lt;br /&gt;
 COPY A:\TEST1\SAMPLE A:\TEST2&lt;br /&gt;
&lt;br /&gt;
The following command copies SAMPLE from one place in the subdirectory \TEST1 on a disk in drive A to another place in the same subdirectory. The copied file is renamed SAMPLE1:&lt;br /&gt;
&lt;br /&gt;
 COPY A:\TEST1\SAMPLE A:\TEST1\SAMPLE1&lt;br /&gt;
&lt;br /&gt;
The next command copies the file SAMPLE from the default directory of a disk in drive B to a printer attached to the first parallel port:&lt;br /&gt;
&lt;br /&gt;
 COPY B:SAMPLE PRN:&lt;br /&gt;
&lt;br /&gt;
The same file can be copied to the printer at the first serial port with the following command:&lt;br /&gt;
&lt;br /&gt;
 COPY B:SAMPLE AUX:&lt;br /&gt;
&lt;br /&gt;
The next command copies all files with names starting with SAMPLE, a period and any extension from a disk in drive A to a disk in drive B. The &#039;&#039;&#039;D&#039;&#039;&#039; in the command causes deleted records to be removed from any of the SAMPLE files which are internal files. The &#039;&#039;&#039;C&#039;&#039;&#039; in the command causes Business Rules! to prompt for a diskette change if the disk in drive B fills up before the copying process is completed.&lt;br /&gt;
&lt;br /&gt;
 COPY A:SAMPLE.* B: -DC&lt;br /&gt;
&lt;br /&gt;
The following example copies the internal file SAMPLE from a disk in drive A to a disk in drive B, setting the record length of the new file at 128:&lt;br /&gt;
&lt;br /&gt;
 COPY A:SAMPLE B: -128&lt;br /&gt;
&lt;br /&gt;
As of 4.2, COPY supports copying a print file unaltered to a printer:&lt;br /&gt;
 COPY PRT\EDITLIST.255  DIRECT:/HP4&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 COPY &amp;lt;from file&amp;gt; {[AUX:]|[PRN:]|[&amp;lt;to file&amp;gt;]} {[&amp;gt;[&amp;gt;]&amp;lt;output file&amp;gt;]|[PRINT]} [&amp;lt;[[copy option|-option]]&amp;gt;][-...]&lt;br /&gt;
&lt;br /&gt;
[[Image:Copy.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
# Copy the file, using the current file name, into the current directory on the current drive.&lt;br /&gt;
# Display a log of all performed actions in the [[command console]].&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
&lt;br /&gt;
Copy requires the &#039;&#039;&#039;from file&#039;&#039;&#039; parameter, which provides the information necessary for identifying the file or files you wish to copy.&lt;br /&gt;
&lt;br /&gt;
After the &#039;&#039;&#039;from file&#039;&#039;&#039;, you may specify the optional parameter &#039;&#039;&#039;to file&#039;&#039;&#039;, which is the name (and path, if required) of the file you are creating.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PRN:&#039;&#039;&#039; allows you to copy the file indicated in the &#039;&#039;&#039;from file&#039;&#039;&#039; to the printer attached to the first parallel port on DOS systems. This string must be translated with the [[SUBSTITUTE]] specification in [[BRConfig.sys]] to specify the appropriate output device on [[Linux]] systems.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;AUX:&#039;&#039;&#039; copies the &#039;&#039;&#039;from file&#039;&#039;&#039; file to the printer attached to the first [[serial port]]. This string must be translated with the SUBSTITUTE specification for Business Rules! Linux versions.&lt;br /&gt;
&lt;br /&gt;
In place of the &#039;&#039;&#039;PRN:&#039;&#039;&#039; or &#039;&#039;&#039;AUX:&#039;&#039;&#039; parameters, you may also specify COM1:, COM2:, LPT1:, LPT2:, etc. to indicate the port to which your printer is attached (see the drive discussion in the Definitions chapter for more information). These device references will operate properly only on Business Rules DOS versions. They may be mapped to new references with the BRConfig.sys file&#039;s SUBSTITUTE specification.&lt;br /&gt;
&lt;br /&gt;
The redirection symbol in the &#039;&#039;&#039;&amp;gt;file-ref&#039;&#039;&#039; parameter saves messages that normally appear on the screen to the specified file. The &#039;&#039;&#039;PRINT&#039;&#039;&#039; parameter sends these same messages to the printer rather than to the screen or a file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Option&#039;&#039;&#039; may be one or more of several optional parameters. Such option(s) can follow the copy command when separated from the rest of the command with a dash:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-A&#039;&#039;&#039;||tells the system to copy only files that have the archive bit on. The system turns the archive bits in the original files off when the copying process is complete. This option applies to Business Rules! [[DOS]] versions only.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-C&#039;&#039;&#039;||prompts for a diskette change if the disk is full.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-D&#039;&#039;&#039;||omits deleted records from the copy, but responds only when used with internal files. It is important to rebuild all related index files after using this option.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-N&#039;&#039;&#039;||prevents the screen from showing a log of performed actions.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-P&#039;&#039;&#039;||causes scrolling to pause after a full screen of information has been displayed. (Press &amp;lt;CR&amp;gt; for next screen.)&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-S&#039;&#039;&#039;||option has been added to allow copying of a file which is currently open. The file to be copied must be opened for sharing (SHR or SHRI) if it is opened NOSHR, error [[4148]] (file sharing violation) will occur when COPY with the -S is attempted.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-V&#039;&#039;&#039;||requires the operator to Verify each action before Business Rules! performs it.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;-#&#039;&#039;&#039;||represents output file record length. Internal file record lengths are shortened or expanded (with blanks) to match the number you indicate (version [[2.04]]+). When specified with other options, the record length must always be last.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
Prior to version [[3.0]] the -S switch meant preserve space.  That syntax is no longer supported. -s now refers to file sharing as noted above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
COPY to a printer :DEVICE was being rejected. COPY to a PRN: or WIN: device is still illegal. (Use TYPE with redirection for PRN: and WIN:)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
While the [Source] Directory is very flexible supporting *, *.* &amp;amp; *.*.* to include &amp;quot;All Files&amp;quot;, the [Destination] Directory is less flexibe.    &lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination&lt;br /&gt;
* This sample will copy all files to Destination - This is the preferred syntax for copy&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination\*&lt;br /&gt;
* This sample will copy all files to Destination but will Remove any Extension so &amp;quot;Sample.wb&amp;quot; will become &amp;quot;Sample&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination\*.*&lt;br /&gt;
* This sample will copy all files to Destination but complex files like &amp;quot;Sample.wb.brs&amp;quot; will become &amp;quot;Sample.wb&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 Copy Source\* Destination\*.*.*&lt;br /&gt;
* This sample will copy all files to Destination complex files like &amp;quot;Sample.wb.brs&amp;quot; will become &amp;quot;Sample.wb.brs&amp;quot;  - Files with more &amp;quot;.&amp;quot; would also be truncated.&lt;br /&gt;
&lt;br /&gt;
==Client Server==&lt;br /&gt;
&lt;br /&gt;
 COPY    filename      @:filename      (@ indicates Client system)&lt;br /&gt;
&lt;br /&gt;
Copies filename TO client current directory&lt;br /&gt;
&lt;br /&gt;
(Or specify @:pathname relative to client current directory.)&lt;br /&gt;
&lt;br /&gt;
 COPY      @:filename      filename&lt;br /&gt;
&lt;br /&gt;
Copies filename FROM client current directory.&lt;br /&gt;
&lt;br /&gt;
If you are trying to use [[absolute paths]] on the [[client]], specify @::&lt;br /&gt;
&lt;br /&gt;
This is really most useful with [[OPEN:]] and [[SAVE:]] file selection browsers.&lt;br /&gt;
&lt;br /&gt;
Use caution when using absolute paths on the client because [[permissions]] and [[file systems]] can be different from one computer to another.&lt;br /&gt;
&lt;br /&gt;
Normally BR will use the [[startup directory]] to copy things and you can change this in the shortcut if needed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:File Management Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11470</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11470"/>
		<updated>2024-09-19T03:22:29Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* File Layouts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the effort associated with making changes to data file layouts. Thanks to the Fileio library, when I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define each of your file layouts in an ASCII text file layout file that is described below. The FileIO Library will parse through your file layouts, and it will instruct your programs how to access the file data after it has been read. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|BR data file form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store the related numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these as follows:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string array elements to 1000 bytes each. This is the length of one field in the color file. The array element length needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all compiloed form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add the following snippet of code to interface with the library. Note that this snippet can be copied in exactly as provided and no customization of it is needed. &lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a procedure file whenever we open a file - to set the names of all of our variables. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:  (same but without line numbers)&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open statement above will do the same thing to the color category file, except the parameter value &amp;quot;1&amp;quot; tells the function to open readonly. The array subscripts assignment procedure file (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
=== File Layouts ===&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
In this example, a farm management system uses a price file to manage seasonal prices:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm Record, a unique string to prefix all of your subscript pointers, and the file version number. The subscript prefix is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11469</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11469"/>
		<updated>2024-09-11T21:52:57Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* File Layouts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the effort associated with making changes to data file layouts. Thanks to the Fileio library, when I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define each of your file layouts in an ASCII text file layout file that is described below. The FileIO Library will parse through your file layouts, and it will instruct your programs how to access the file data after it has been read. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|BR data file form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store the related numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these as follows:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string array elements to 1000 bytes each. This is the length of one field in the color file. The array element length needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all compiloed form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add the following snippet of code to interface with the library. Note that this snippet can be copied in exactly as provided and no customization of it is needed. &lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a procedure file whenever we open a file - to set the names of all of our variables. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:  (same but without line numbers)&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open statement above will do the same thing to the color category file, except the parameter value &amp;quot;1&amp;quot; tells the function to open readonly. The array subscripts assignment procedure file (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
In this example, a farm management system uses a price file to manage seasonal prices:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm Record, a unique string to prefix all of your subscript pointers, and the file version number. The subscript prefix is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11468</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11468"/>
		<updated>2024-09-11T15:49:43Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* How it Works */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the effort associated with making changes to data file layouts. Thanks to the Fileio library, when I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define each of your file layouts in an ASCII text file layout file that is described below. The FileIO Library will parse through your file layouts, and it will instruct your programs how to access the file data after it has been read. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|BR data file form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store the related numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these as follows:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string array elements to 1000 bytes each. This is the length of one field in the color file. The array element length needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all compiloed form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add the following snippet of code to interface with the library. Note that this snippet can be copied in exactly as provided and no customization of it is needed. &lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a procedure file whenever we open a file - to set the names of all of our variables. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:  (same but without line numbers)&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open statement above will do the same thing to the color category file, except the parameter value &amp;quot;1&amp;quot; tells the function to open readonly. The array subscripts assignment procedure file (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11467</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11467"/>
		<updated>2024-09-11T15:34:46Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* fnOpen Function */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the effort associated with making changes to data file layouts. Thanks to the Fileio library, when I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define each of your file layouts in an ASCII text file layout file that is described below. The FileIO Library will parse through your file layouts, and it will instruct your programs how to access the file data after it has been read. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|BR data file form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store the related numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these as follows:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string array elements to 1000 bytes each. This is the length of one field in the color file. The array element length needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all compiloed form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add the following snippet of code to interface with the library. Note that this snippet can be copied in exactly as provided and no customization of it is needed. &lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a procedure file whenever we open a file - to set the names of all of our variables. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:  (same but without line numbers)&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open above will do the same thing to the color category file, except the 1 parameter value tells the function to open readonly. The array subscripts populate procedure (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11466</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11466"/>
		<updated>2024-09-11T14:36:29Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the effort associated with making changes to data file layouts. Thanks to the Fileio library, when I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define each of your file layouts in an ASCII text file layout file that is described below. The FileIO Library will parse through your file layouts, and it will instruct your programs how to access the file data after it has been read. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|BR data file form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store the related numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these as follows:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string array elements to 1000 bytes each. This is the length of one field in the color file. The array element length needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all compiloed form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add a snippet of code to interface with the library.&lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a procedure file whenever we open a file - to set the names of all of our variables. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open above will do the same thing to the color category file, except the 1 parameter value tells the function to open readonly. The array subscripts populate procedure (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11465</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11465"/>
		<updated>2024-09-11T13:35:12Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the effort associated with making changes to data file layouts. Thanks to the Fileio library, when I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define each of your file layouts in an ASCII text file layout file that is described below. The FileIO Library will parse through your file layouts, and it will instruct your programs how to access the file data after it has been read. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and initializing of the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|file layout form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store all the numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these to:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string arrays to 1000. This is the length of one field in the color file. It needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01234    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add a snippet of code to interface with the library.&lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a proc file whenever we open a file to set the names of all our variables after we return. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open above will do the same thing to the color category file, except the 1 parameter value tells the function to open readonly. The array subscripts populate procedure (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11464</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11464"/>
		<updated>2024-09-11T13:26:07Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* How it Works */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the headache associated with making changes to data files. In my experiences working for [[BRC]], I had to make a change to the commun file, a data file that stores information about communication with EDI VANs. I added some new fields, and removed a couple of old fields, and then I began searching through the BRC program suite to find and modify each program that referenced this data file in order to update it with the proper new form statement. This task quickly became daunting as I noticed that out of BRC’s program suite of over 600 programs, more than one third of them referenced the commun file. With 223 programs to modify, the task was given up as hopeless and tabled indefinitely.&lt;br /&gt;
&lt;br /&gt;
I am happy to announce that we have solved this problem. When I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define your file layouts in an ASCII text file layout file that I will tell you about in a minute. The FileIO Library will actually parse through your file layouts, and it will instruct your programs how to read the data file, so that you don’t have to. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it even contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and initializing of the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|file layout form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store all the numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these to:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string arrays to 1000. This is the length of one field in the color file. It needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01234    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add a snippet of code to interface with the library.&lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a proc file whenever we open a file to set the names of all our variables after we return. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open above will do the same thing to the color category file, except the 1 parameter value tells the function to open readonly. The array subscripts populate procedure (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11463</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11463"/>
		<updated>2024-09-11T13:24:42Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* How it Works */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the headache associated with making changes to data files. In my experiences working for [[BRC]], I had to make a change to the commun file, a data file that stores information about communication with EDI VANs. I added some new fields, and removed a couple of old fields, and then I began searching through the BRC program suite to find and modify each program that referenced this data file in order to update it with the proper new form statement. This task quickly became daunting as I noticed that out of BRC’s program suite of over 600 programs, more than one third of them referenced the commun file. With 223 programs to modify, the task was given up as hopeless and tabled indefinitely.&lt;br /&gt;
&lt;br /&gt;
I am happy to announce that we have solved this problem. When I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define your file layouts in an ASCII text file layout file that I will tell you about in a minute. The FileIO Library will actually parse through your file layouts, and it will instruct your programs how to read the data file, so that you don’t have to. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it even contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and initializing of the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|file layout form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store all the numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these to:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string arrays to 1000. This is the length of one field in the color file. It needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01234    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add a snippet of code to interface with the library.&lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a proc file whenever we open a file to set the names of all our variables after we return. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and READ it to find out what the Color File looks like. Then it OPENs the Color File, and sets MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data read into the 2 arrays, and it will assign the file handle to a variable called &amp;quot;colorfile&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The second open above will do the same thing to the color category file, except the 1 parameter value tells the function to open readonly. The array subscripts populate procedure (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11462</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11462"/>
		<updated>2024-09-11T13:19:08Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* How it Works */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the headache associated with making changes to data files. In my experiences working for [[BRC]], I had to make a change to the commun file, a data file that stores information about communication with EDI VANs. I added some new fields, and removed a couple of old fields, and then I began searching through the BRC program suite to find and modify each program that referenced this data file in order to update it with the proper new form statement. This task quickly became daunting as I noticed that out of BRC’s program suite of over 600 programs, more than one third of them referenced the commun file. With 223 programs to modify, the task was given up as hopeless and tabled indefinitely.&lt;br /&gt;
&lt;br /&gt;
I am happy to announce that we have solved this problem. When I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define your file layouts in an ASCII text file layout file that I will tell you about in a minute. The FileIO Library will actually parse through your file layouts, and it will instruct your programs how to read the data file, so that you don’t have to. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it even contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and initializing of the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|file layout form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store all the numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these to:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string arrays to 1000. This is the length of one field in the color file. It needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01234    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add a snippet of code to interface with the library.&lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a proc file whenever we open a file to set the names of all our variables after we return. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
The above statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then Open the Color File, and set MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data we after reading it, and it will place the file handle into a variable called colorfile.&lt;br /&gt;
&lt;br /&gt;
The second open above will do the same thing to the color category file, except the 1 parameter value tells the function to open readonly. The array subscripts populate procedure (passed back from Fileio) will be executed, thereby assigning values to the subscript names in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11461</id>
		<title>FileIO Library</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=FileIO_Library&amp;diff=11461"/>
		<updated>2024-09-11T05:32:46Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Method of Operation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;FileIO&#039;&#039;&#039; Library began as a project to find a way to reduce the headache associated with making changes to data files. In my experiences working for [[BRC]], I had to make a change to the commun file, a data file that stores information about communication with EDI VANs. I added some new fields, and removed a couple of old fields, and then I began searching through the BRC program suite to find and modify each program that referenced this data file in order to update it with the proper new form statement. This task quickly became daunting as I noticed that out of BRC’s program suite of over 600 programs, more than one third of them referenced the commun file. With 223 programs to modify, the task was given up as hopeless and tabled indefinitely.&lt;br /&gt;
&lt;br /&gt;
I am happy to announce that we have solved this problem. When I have to make a change to a data file layout for my customers today, I can complete the task in under 1 minute, and not a single program needs to be changed in order to continue working with the new file layout. In fact, I don’t even have to update my customer’s data files, as the FileIO library takes care of this for me as well.&lt;br /&gt;
&lt;br /&gt;
The trick is to define your file layouts in an ASCII text file layout file that I will tell you about in a minute. The FileIO Library will actually parse through your file layouts, and it will instruct your programs how to read the data file, so that you don’t have to. It will also automatically detect when you make changes to the file layout, and it will update your customer’s data files on the fly to make sure they have the latest version. Finally, it even contains a DataCrawler that you can use to examine the contents of any of your BR data files in a raw format.&lt;br /&gt;
&lt;br /&gt;
For more information about the FileIO Library visit the [http://www.sageax.com/products/fileio-library/ Sage AX Website]&lt;br /&gt;
&lt;br /&gt;
To download the latest copy of FileIO, click [http://www.sageax.com/downloads/FileIO.zip here.]&lt;br /&gt;
&lt;br /&gt;
== Method of Operation ==&lt;br /&gt;
The file IO library parses a formatted text file layout and uses this information to structure the opening and initializing of the reading of a file “object”. The word object here is used to refer to all the data in a given record layout in one of your files. This library is easy to use, and provided you follow some simple standards, it will simplify your life immensely.&lt;br /&gt;
&lt;br /&gt;
Your programs will need to define one array for all [[Form|file layout form statements]] and add a snippet of code (given below) to the program for interfacing with with the library. For each file you will need to define a couple of arrays and use the library to open them. From that point on access to data is simple and direct. &lt;br /&gt;
&lt;br /&gt;
=== File Object Arrays ===&lt;br /&gt;
&lt;br /&gt;
First, in your program you must create two arrays to store the file information. If we are dealing with the Color File these would be MAT COLOR$ and MAT COLOR. One will store all the string information about a color, and the other will store all the numeric data. Our working example will involve two files: a Color File and a Color Category File. You should dimension these to:&lt;br /&gt;
&lt;br /&gt;
  01030    DIM color$(1)*1000,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1000,colorcat(1)&lt;br /&gt;
&lt;br /&gt;
We&#039;re dimensioning the string arrays to 1000. This is the length of one field in the color file. It needs to be at least as big as the largest string field in the data file.&lt;br /&gt;
&lt;br /&gt;
However, it is recommended to make sure the length is long enough to handle any field you might eventually add to the file at any point in the future. BR supports multi-line textboxes, so I sometimes add long memo fields to my data files that might be 400 or 800 or even 1000 characters long, therefore 1000 is the length I use now in all my new development.&lt;br /&gt;
&lt;br /&gt;
But anything will work as long as its long enough to handle the largest data field in your file.&lt;br /&gt;
&lt;br /&gt;
=== Forms Array ===&lt;br /&gt;
&lt;br /&gt;
Your programs will need a string variable array to store the form statements associated with reading files. This form statements array will be dynamically generated or expanded whenever the a file is opened. It will say: &lt;br /&gt;
&lt;br /&gt;
  01234    DIM form$(1)*255&lt;br /&gt;
&lt;br /&gt;
This form statement will be compiled by the FileIO open statement. BR limits all form statements to 255 bytes, so 255 is sufficient to hold the compiled Forms$ statements.&lt;br /&gt;
&lt;br /&gt;
=== Library Linkage ===&lt;br /&gt;
&lt;br /&gt;
You will also need the library statement:&lt;br /&gt;
&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
&lt;br /&gt;
=== fnOpen Function ===&lt;br /&gt;
&lt;br /&gt;
Now, add a snippet of code to interface with the library.&lt;br /&gt;
&lt;br /&gt;
Because BR libraries do not share global variables with the programs they are called from, we will need to execute a proc file whenever we open a file to set the names of all our variables after we return. Add the following snippet of code into your program. It will create an FnOpen function that calls the library and runs the proc file. The $$$ at the end of the procfile name tells the procfile to self-destruct after execution. Since this procfile is used simply to return variable information back from the library to the main program, we don’t need it sitting around collecting dust.&lt;br /&gt;
&lt;br /&gt;
  99010 OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
  99020    def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
  99030       dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
  99040       let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
  99050       if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
  99060    fnend&lt;br /&gt;
&lt;br /&gt;
Or, for those with [[Lexi]]:&lt;br /&gt;
  OPEN: ! ***** Function To Call Library Openfile And Proc Subs&lt;br /&gt;
     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths,Supress_Prompt,Ignore_Errors,Suppress_Log,___,Index)&lt;br /&gt;
        dim _FileIOSubs$(1)*800, _Loadedsubs$(1)*80&lt;br /&gt;
        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$, Supress_Prompt,Ignore_Errors,Program$,Suppress_Log)&lt;br /&gt;
        if Srch(_Loadedsubs$,Uprc$(Filename$))&amp;lt;=0 then : mat _Loadedsubs$(Udim(_Loadedsubs$)+1) : let _Loadedsubs$(Udim(_Loadedsubs$))=Uprc$(Filename$) : for Index=1 to Udim(Mat _Fileiosubs$) : execute (_Fileiosubs$(Index)) : next Index&lt;br /&gt;
     fnend&lt;br /&gt;
&lt;br /&gt;
=== Using FileIO in Your Programs ===&lt;br /&gt;
Now you are ready to process files. &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;You simply open the files by saying:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;read each file as follows:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore  &lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;and access the data:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name)&lt;br /&gt;
&lt;br /&gt;
=== How it Works ===&lt;br /&gt;
These statements tell the File Library to look in the filelay folder to find the file layout for the Color File, and read it to find out what the Color File looks like. Then Open the Color File, and set MAT COLOR$ and MAT COLOR to the correct number of elements to hold an entire color record. Finally, it will define several variables that we can use as pointers (subscripts) into these arrays to access the data we after reading it, and it will place the file handle into a variable called colorfile.&lt;br /&gt;
&lt;br /&gt;
The second open above will do the same thing to the color category file, except the 1 parameter tells the function to open readonly. The subscripts array will be executed, creating the subscripts in memory, and the pointers (subscripts) will be set in the calling program. So, if I had a file layout such as the following:&lt;br /&gt;
&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
Then the functions would do the same thing as the following individual lines of code (assuming the next available file handle was 5):&lt;br /&gt;
&lt;br /&gt;
  DIM FORM$(5)*255&lt;br /&gt;
  DIM COLOR$(9)*1023, COLOR(1)&lt;br /&gt;
  OPEN #5: “Name=color.dat, kfname=color.key”,internal,outin,keyed&lt;br /&gt;
  LET FORM$(5)=”form C 6,V 30,V 6,C 6”&lt;br /&gt;
  LET FORM$(5)=CFORM$(FORM$(5))&lt;br /&gt;
  LET COLORFILE=5&lt;br /&gt;
  CO_CODE=1&lt;br /&gt;
  CO_NAME=2&lt;br /&gt;
  CO_CATEGORY=3&lt;br /&gt;
  CO_HTML=4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The data is used by referencing the file array, with the subscript name, so the color’s description becomes color$(co_Name).&lt;br /&gt;
&lt;br /&gt;
In the old days, if we wanted to change the file layout of our file, all of the programs that used that file would have to be changed one at a time to use the new file layout. Also, if the order of the fields changed, then all the programs would have to also be changed to use the new fields.&lt;br /&gt;
&lt;br /&gt;
But now, using this function, we can change the file layout all we want. If we later want to insert a field into the file layout before color name, I won’t have to look at this program again; this program will just work fine, because the library maps the subscripts to the actual fields.&lt;br /&gt;
&lt;br /&gt;
Also, the code is easier to read and maintain.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The whole thing looks like this:&lt;br /&gt;
&lt;br /&gt;
  01025    ! Dimension the Arrays&lt;br /&gt;
  01030    DIM color$(1)*1023,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*1023,colorcat(1)&lt;br /&gt;
  01050    DIM form$(1)*255&lt;br /&gt;
  02000 !&lt;br /&gt;
  02020 library &amp;quot;fileio.br&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$) ! Open the file&lt;br /&gt;
  05000 ! &lt;br /&gt;
  30120    read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore ! Read the file&lt;br /&gt;
  30180    LET ColorCode$=color$(co_Code) !:&lt;br /&gt;
           LET ColorName$=color$(co_Name) ! Use the data by referincing it in the file arrays&lt;br /&gt;
  80000 !&lt;br /&gt;
  90000 ! Every program using fileio needs the following code&lt;br /&gt;
  99010  OPEN: ! ***** Function To Call Library Openfile And Generate Subs&lt;br /&gt;
  99020     def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, Dont_Sort_Subs, Path$*255, Mat Descr$, Mat Field_Widths)&lt;br /&gt;
  99030        dim _FileIOSubs$(1)*800&lt;br /&gt;
  99040        let Fnopen=Fnopenfile(Filename$, Mat F$, Mat F, Mat Form$, Inputonly, Keynum, Dont_Sort_Subs, Path$, Mat Descr$, Mat Field_Widths, Mat _FileIOSubs$)&lt;br /&gt;
  99050        for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  99060     fnend&lt;br /&gt;
&lt;br /&gt;
== File Layouts ==&lt;br /&gt;
Now lets inspect the anatomy of a properly formatted file layout. These should be placed in a subdirectory called filelay.&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
&lt;br /&gt;
The first line contains the name of the Farm File, a unique string to prefix all of your subscript pointers, and the file version number. The subscript value is to ensure that the program knows the difference between the Farm File’s Farm Code, and the Route File’s Route Code. (One will be RT_CODE and the other is FM_CODE).  These are separated by a comma. Spacing does not matter, so adjust your spacing so that it looks nice.&lt;br /&gt;
&lt;br /&gt;
The Version number is used to determine when the data has changed, and an update needs to be made to your data file. Your file layouts should all start at version 0, and each time you want to make a change, you may increment this value by 1.&lt;br /&gt;
&lt;br /&gt;
Each time you open a file with the FILEIO library, it reads the file version number of the file on disk (using the BR version() function), and then it compares it to the version number in your file layout. If your file layout has a higher version number then your existing data file, then the library opens the backup of the file layout for the version of the data file that exists on disk. It makes a backup copy of the existing data file, and creates a new file with the proper version number. Then it reads your records one at a time, and copies all the data from the old file into the new file one field at a time. If a field is dropped from the file layout, that data gets lost. If fields are rearranged, the data is copied and saved in the new file in the new positions. If a new field is added, it starts out blank (or 0).&lt;br /&gt;
&lt;br /&gt;
If you look in the filelay folder, you will notice a version folder. This contains several files ending with a number. For example you may see a color.0, and a color.1 file.&lt;br /&gt;
&lt;br /&gt;
If you run the fnopen function and it can not find your data file (usually because this is a new file and it hasn’t been created yet), &#039;&#039;the library will automatically create your file,&#039;&#039; based on the information you give in the first part of the file layout.&lt;br /&gt;
&lt;br /&gt;
Also, whenever you make changes to your file layout, the function library will automatically update the data files on disk for you. It does this by renaming the old file, creating a new one with the new version number, and then copying the data from the old one to the new one a record at a time.&lt;br /&gt;
&lt;br /&gt;
Any time it creates a file, or updates the file on disk, it creates a backup of the file &#039;&#039;layout&#039;&#039;. The first time you run a program that tries to read your new data file, it will create the data file for you and make a backup of the layout, and if the number in your file layout is 0, then it will create, for example, a filelay\version\price.0 file, a backup of your file layout that the library uses to figure out what has changed the next time you try to update the file.&lt;br /&gt;
&lt;br /&gt;
If you wish to make any changes to this data file, first you increment the version number, (in this case make the 0 into a 1). Then change the file layout all you want. You may rearrange fields, add new fields, add or remove keys, or change the record length.&lt;br /&gt;
&lt;br /&gt;
The only step necessary for having your data files updated is to increment the version number every time you make a change to the file layout.&lt;br /&gt;
&lt;br /&gt;
You may make any changes you like to the file layouts, but do not change the names of your existing subscripts. If you change the subscript name, not only will all the programs that reference that subscript be broken, but additionally, the routine will not understand that you are changing the name. It will think you are dropping the old field and adding a new field and any data stored in this location will be lost when the file is updated.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
&lt;br /&gt;
The second line contains the name of the first key, and a definition telling what the key is based on. These are separated by a comma. The key definition is made up of each of the subscript names in this file that form the keyfields for the file. When the library is called to open a file, if the file does not exist, or if it needs to be updated, a new one will be created. The function will read these subscript names that form your key definitions, and it will calculate the proper key position (KPS=) and length (KLN=) values. Then the create routine will use this information with the Index command to create the new key files.&lt;br /&gt;
&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  price.ky4, DESCRIPTION-U/COST&lt;br /&gt;
  recl=127&lt;br /&gt;
&lt;br /&gt;
Notice that the third key is based on three different fields. These are separated by a “/”. It is important that you use a “/” to separate your keys when you are building a key out of more then one field, because the function uses this “/” to help it make the proper BR syntax for defining complex key fields.&lt;br /&gt;
&lt;br /&gt;
Also, note the &amp;quot;-U&amp;quot; at the end of the fieldname in the 4th key. This indicates that the &amp;quot;Description&amp;quot; part of that key is case insensitive. This translates to using the &amp;quot;U&amp;quot; on part of your key spec in Business Rules.&lt;br /&gt;
&lt;br /&gt;
The file can have as many keys as you want. The function will keep parsing key file names until it reaches the next part of the layout. It opens any OutIn files with all keys so that any changes you may make to the data stored on disk will be properly reflected in all the key files.&lt;br /&gt;
&lt;br /&gt;
The optional RECL= Parameter is read and used whenever a new file is created or an old one is updated. If it is not specified, the record length is calculated from the fields in the layout.&lt;br /&gt;
&lt;br /&gt;
  ===================================================&lt;br /&gt;
&lt;br /&gt;
The next line in the file is skipped by the parsing routine, and its purpose is to make the file layouts more readable to a programmer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
&lt;br /&gt;
Following the file and key structure, the fields are defined. There are three parameters on each field definition line, and you make one line for each field in your file layout. The first parameter is the subscript name that you will use in your program to refer to this data element. Place a $ at the end of the subscript name for all string elements. Don’t place anything at the end of the subscript name for numeric elements. Note that each of these names will be prefixed by the second parameter of the very first line of this layout. &lt;br /&gt;
&lt;br /&gt;
The second parameter is the description. This description is for the benefit of the programmer, so when the programmer is reading the file layouts, (s)he can tell which field does what. The spaces are ignored, so you may include as many spaces as you wish to make the layout look good. It does seem like good general guidelines would be to limit your layout lines to 255 characters and your descriptions to 80. &lt;br /&gt;
&lt;br /&gt;
The description is also used by the built in DataCrawler, a program that reads any of your data files and displays the entire contents in raw form in a listview. The Descriptions become the headings for each column in the listview. &lt;br /&gt;
&lt;br /&gt;
Those descriptions are also used for the default captions for your fields if you use ScreenIO, a library for building GUI programs that itself builds off of fileio.&lt;br /&gt;
&lt;br /&gt;
The third parameter is the form statement, which is pretty straightforward. Any items with a form statement of type “X” will be ignored, except that the length will still be used to calculate the position on disk of all remaining fields in the record. The library will take X fields into consideration when building the form statement, but not at any other time.&lt;br /&gt;
&lt;br /&gt;
The fourth parameter is optionally a [[#Support for Dates|Disk Date Format]]. You can specify DATE(Julian) if you&#039;re storing your dates in Julian format on disk. Or you can specify DATE(cymd) or DATE(ymd) or DATE(mdy) or any other valid date spec here, and FileIO will treat this field like a date.&lt;br /&gt;
&lt;br /&gt;
Your programs will still have to unpack it for you, fileio can&#039;t do that because fileio doesn&#039;t read the file for you.&lt;br /&gt;
&lt;br /&gt;
However, the data crawler, the automatic updates, and screenIO, are all compatible with these dates specified in your file layout.&lt;br /&gt;
&lt;br /&gt;
If the fourth parameter is anything other then DATE(format), then its treated as a comment and ignored.&lt;br /&gt;
&lt;br /&gt;
The fifth and all later columns, if any, are treated as comments and ignored.&lt;br /&gt;
&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
Comment line text must begin with an exclamation point, but it may be indented. Comment lines may appear anywhere (vertically) and are ignored. &amp;lt;br&amp;gt;&lt;br /&gt;
Blank lines are ignored as well.&lt;br /&gt;
&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
After the last field definition, an &#039;&#039;optional&#039;&#039; #eof# line may be specified, in which case any lines after that will be ignored. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  ! default cost is normally zero&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
  #eof#&lt;br /&gt;
  additional comments...&lt;br /&gt;
&lt;br /&gt;
And that’s all there is to it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Support for Dates ===&lt;br /&gt;
As of V2.48, file layouts support dates in any storage formats on disk. Whether you store your dates in Julian or in YMD or MDY or any other format, specify so in the 4th column of your file layouts, using the FileIO Date Keyword. ex: DATE(Julian) or DATE(YMD) or any other valid BR Date Format.&lt;br /&gt;
&lt;br /&gt;
[[File:Dates0.png]]&lt;br /&gt;
&lt;br /&gt;
When this is specified, it enables the FileIO data exploration tool (Data Crawler) to display the dates in human readable format. This works for both Viewing, and for Editing.&lt;br /&gt;
&lt;br /&gt;
The new Date functionality also supports the File Layout Upgrade facility, allowing you to change the format of your dates on disk and FileIO will handle it automatically, upgrading the field to use the new date format for you.&lt;br /&gt;
&lt;br /&gt;
It works also for Exporting your data files to CSV, and is supported automatically in the FileIO functions that do those exports. It converts the dates on export to a standard format (Default: m/d/cy) that you can specify in your FileIO.Ini File.&lt;br /&gt;
&lt;br /&gt;
And it extends ScreenIO&#039;s date features to all date fields, no matter what format they&#039;re stored in. (Previously, ScreenIO&#039;s advanced date features only worked for dates stored in Julian on disk.)&lt;br /&gt;
&lt;br /&gt;
This update adds two new ini file options:&lt;br /&gt;
 CODE: SELECT ALL&lt;br /&gt;
 DateFormatExport$=&#039;m/d/cy&#039;  ! Format of Dates when Exporting to CSV&lt;br /&gt;
 DateFormatDisplay$=&#039;m/d/cy&#039; ! Format of Dates when displaying in Data Crawler&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use these to specify your preferred Date format for Viewing/Editing, and your preferred Date format for Exporting.&lt;br /&gt;
&lt;br /&gt;
Remember, you can see the full list of FileIO ini file options, by looking in the source code at the top of FileIO.&lt;br /&gt;
&lt;br /&gt;
There is a new optional parameter for all layout reading functions, mat NDateSpec$ and mat SDateSpec$ which correspond with the other arrays, to let you know which fields are date fields, so that you can develop routines in your programs to automatically pack and unpack the dates.&lt;br /&gt;
&lt;br /&gt;
Important Note: Using FileIO, the reading of the data file is still done directly in your programs. So this does not change how your existing programs work. It does not unpack the dates for you in your programs. You still have to do all that.&lt;br /&gt;
&lt;br /&gt;
For this reason, upgrading to the new Date processing will not break any of your current code.&lt;br /&gt;
&lt;br /&gt;
=== Debugging Tips ===&lt;br /&gt;
&lt;br /&gt;
When you&#039;re making new layouts, a missing comma can cause FileIO to parse the layout wrong and give you errors. Here are a couple tips to help ensure your layouts are error free before using them in your programs.&lt;br /&gt;
&lt;br /&gt;
*Use the Data Crawler to test your layout. The data crawler is the simplest way to test your new layout without writing any code. It opens it and accesses the file in a very simple and straight forward manor to help identify any errors in the layout that you might have.&lt;br /&gt;
&lt;br /&gt;
*If you have trouble with the form statement, you can&#039;t see whats in it because FileIO uses CForm$ to compile your form statement to make your programs run faster. But here&#039;s a way to tell what the original form statement was that FileIO generated when it put the strings first and numbers last in your program: Open the file in the Data Crawler. When you get an error, type &amp;quot;Print FormStatement$&amp;quot; to see the original form statement.&lt;br /&gt;
&lt;br /&gt;
This works even if your file is working fine. Any time the file is opened with the data crawler, you can press CTRL-A and then type PRINT FormStatement$ and it will print out the uncompiled form statement for the file.&lt;br /&gt;
&lt;br /&gt;
== Utilities ==&lt;br /&gt;
&lt;br /&gt;
Now that you have your file layouts defined, you have access to several powerful utilities right out of the box using Fileio. Additionally, several more utilities are available as [[#FileIO_Add-on_Packages|Add-Ons]], including our most popular development tool [[Screenio|ScreenIO]]. You can read more about them in their section below.&lt;br /&gt;
&lt;br /&gt;
But for now, lets take a look at some of the wonderful free utilities that come built into Fileio.&lt;br /&gt;
&lt;br /&gt;
Some of these utilities are accessible from your code via library functions, but the primary way you access any of them is by simply loading the FileIO Library and running it directly.&lt;br /&gt;
&lt;br /&gt;
=== DataCrawler ===&lt;br /&gt;
The data crawler is the original utility of FileIO. This utility shows you first a list of all data files allowing you to select one.&lt;br /&gt;
&lt;br /&gt;
Select a data file and press enter, and FileIO will then display a listview containing all the data in the data file. This list is sized based on the size of your main BR window (window 0) automatically to take up the full size available to it, so if you want to see more, try [[Open Window|opening window 0]] larger before running FileIO.&lt;br /&gt;
&lt;br /&gt;
At the top of the window is a filter box, and you can type anything you want in there and click &amp;quot;Refresh&amp;quot; and it will reread the data file, shortening the list to show only those records that match (case insensitive) what you have typed.&lt;br /&gt;
&lt;br /&gt;
If you have ScreenIO installed, then FileIO&#039;s data crawler will take advantage of the included Animation Engine in ScreenIO to animate a loading window while the listview is displaying. If you don&#039;t have ScreenIO then FileIO will simply load the listview.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to wait for the entire file to load, press ESC to stop the load process.&lt;br /&gt;
&lt;br /&gt;
If the file is empty, FileIO will display a message box letting you know.&lt;br /&gt;
&lt;br /&gt;
This tool is very useful for sorting out data errors. It will allow you to look inside any data file you have a layout for and directly view the data there.&lt;br /&gt;
&lt;br /&gt;
At the bottom of the Data Crawler are several buttons. There&#039;s a Jump button that repositions the file by a key you specify and then loads the list with the data from that key on down to the end of the file.&lt;br /&gt;
&lt;br /&gt;
There is a &amp;quot;Columns&amp;quot; button which you can use to decide which columns should show up on the listview. Fewer columns means faster loading so sometimes for large files I press ESC immediately when the file first starts loading to cancel the load. Then I click &amp;quot;Columns&amp;quot; and check only the columns I want to see. Finally I press the &amp;quot;Refresh&amp;quot; button to trigger it to read the file again and this time it loads much faster then before.&lt;br /&gt;
&lt;br /&gt;
Next you&#039;ll find an Export button which starts the process for exporting a file to CSV. You can read more on that in the next section. And next to that is a Quit button.&lt;br /&gt;
&lt;br /&gt;
In this example, we loaded the file &amp;quot;Read Only&amp;quot; so it put everything in a listview. But there are times when its useful to fix data errors this way too, especially during development. So if you want to directly edit your data file, on the first page select the file you want to edit and instead of pressing &amp;quot;Enter&amp;quot; or clicking &amp;quot;View&amp;quot;, this time press &amp;quot;F5&amp;quot;. F5 is the secret Edit button that loads a data file into a grid instead.&lt;br /&gt;
&lt;br /&gt;
You can use the grid to change records, delete them or add them, and in addition to the buttons listed above, it also has an &amp;quot;Import&amp;quot; button for importing a CSV file into this data file.&lt;br /&gt;
&lt;br /&gt;
Any changes you make to the data file are not saved until you click the &amp;quot;Save&amp;quot; button (which also only appears when you&#039;re in Edit mode).&lt;br /&gt;
&lt;br /&gt;
In the 01/2015 release of FileIO, each records rec # is displayed in the listview, to aid in debugging bad data problems.&lt;br /&gt;
&lt;br /&gt;
==== Debugging Tip ====&lt;br /&gt;
Any time you&#039;re viewing a file layout in the Data Crawler, you can see the Uncompiled Form Statement by pressing CTRL-A to get to an ATTN prompt, and then entering the command:&lt;br /&gt;
&lt;br /&gt;
  print FormStatement$&lt;br /&gt;
&lt;br /&gt;
and it will print out the original form statement.&lt;br /&gt;
&lt;br /&gt;
==== Another Debugging Tip ====&lt;br /&gt;
&lt;br /&gt;
The FileIO Datacrawler ignores records that it cannot read. This is to allow for data files that have multiple record layouts in one data file. You make a custom layout for each record type and the data crawler shows just the records that match that type in the file.&lt;br /&gt;
&lt;br /&gt;
However that becomes a problem when you&#039;re making a layout to work with an existing data file. Error 726 indicates that something minor in the file layout doesn&#039;t match the data on the disk, but these errors are ignored so you might see either an empty data file, or a data file with some records missing. If that happens, you need to see the missing records in order to figure out what is wrong with your layout. &#039;&#039;&#039;&#039;&#039;It&#039;s very important that you don&#039;t try to use a file layout that isn&#039;t quite working right. Make sure your layout works and can read every record of a file that it should read before using it.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To see the records that could not be read in a file, run the data crawler on the layout, then press CTRL-A to get an ATTN prompt. From there, enter the command&lt;br /&gt;
&lt;br /&gt;
  print mat BadRead$&lt;br /&gt;
&lt;br /&gt;
and it will print all the records that were ignored because the file layout didn&#039;t match them. It prints out the raw data from the file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to use Data Crawler in your programs ====&lt;br /&gt;
You can use the data crawler in your own programs by using one of the following functions:&lt;br /&gt;
* [[#fnDataCrawler|fnDataCrawler]] - Open a Listview showing the data file&lt;br /&gt;
* [[#fnDataEdit|fnDataEdit]] - Display the data in an editable grid&lt;br /&gt;
* [[#fnShowData|fnShowData]] - This function does the same thing as the above two, but with many many more options allowing you to customize exactly what appears and exactly what they&#039;re allowed to change.&lt;br /&gt;
&lt;br /&gt;
Note: its not recommended to allow your customers to use the data crawler to access your data files directly. There&#039;s no way to specify validations or do anything complicated, so unless you&#039;re careful, there are lots of ways your customers could use this tool to do damage to your data files. It is a programmer tool only.&lt;br /&gt;
&lt;br /&gt;
If you do decide to use it for your end users, be careful how you implement it.&lt;br /&gt;
&lt;br /&gt;
=== Import/Export to CSV ===&lt;br /&gt;
&lt;br /&gt;
You can use FileIO to export your data files to CSV or import from CSV into your data file. To do this, you want to run the data crawler and select the file layout you&#039;re looking for. Then, view it (or press F5 to edit if you want to import something). Click on the Export button and it will ask you to select a file. Once that is selected it will ask a couple other simple questions, and when you&#039;ve specified the options to your satisfaction, click the Export! button. A new CSV file will be created.&lt;br /&gt;
&lt;br /&gt;
Import is even more simple. To import, you must be in Edit mode (press F5 to select the file instead of the enter key) and then simply select the Import button. It will ask you again to choose the file, and then it will ask you if it should update all files by Record Number (if record number is recorded in the CSV file) or by Key (if the file has keys) or to just Add all information to the file. Select what you want and press Import and the CSV file is added to the BR data file.&lt;br /&gt;
&lt;br /&gt;
==== Function Access to Import/Export from your programs ====&lt;br /&gt;
&lt;br /&gt;
You can use the following functions to call the Import and Export functionality from your code.&lt;br /&gt;
&lt;br /&gt;
*[[#fnCSVImport|fnCSVImport]]&lt;br /&gt;
*[[#fnCSVExport|fnCSVExport]]&lt;br /&gt;
&lt;br /&gt;
These functions are intended to give you the ability to use our functionality to write your own import and export routines for your customers, because its not a good idea to let your customers run the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
=== Generate Layout Wizard ===&lt;br /&gt;
&lt;br /&gt;
There is also a Generate Layout Wizard utility in FileIO. This utility is there to help you build your file layouts. It is designed to work with a certain style of BR programming that was common in the 80s and 90s. So if your software is written in a style that opens the file directly, and reads/writes the data to a long series of individual variables, the Generate Layout Wizard is for you.&lt;br /&gt;
&lt;br /&gt;
To use it, start by running FileIO. Then, don&#039;t select a layout - instead, click on the &amp;quot;Generate Layout&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll see a screen with a bunch of large text boxes, a small grid, and some buttons.&lt;br /&gt;
&lt;br /&gt;
The first thing you want to do is select the &amp;quot;Browse&amp;quot; button and then select a .wb or .br file that contains a program that reads or writes Most of the File.&lt;br /&gt;
&lt;br /&gt;
When you select one, press the Scan All button and it fileio will search the whole program looking first for all the open strings. It will take the file that is opened the most times and then search for all read statements to that file. It will look for the longest read statement and then find the Form statement associated with that Read statement, and any DIM statements for any variables used.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t like the file its chosen, click the &amp;quot;Open Scratch Pad&amp;quot; button to open the temp work file it uses into the program of your choice (I use myEdit for BRS files). Simply select all the open statements for the file you DO want to build off of, then click the Paste button to paste those open statements into the &amp;quot;Open String&amp;quot; list. After that click &amp;quot;Clear All&amp;quot; to erase what it did on the first search and then click &amp;quot;Scan All&amp;quot; again to rescan the program using this new data file.&lt;br /&gt;
&lt;br /&gt;
You may have to help it a bit, but once it has a proper matching open statement, read statement, form statement and dim statements for any arrays used, press the &amp;quot;Generate Layout&amp;quot; button and it will build a layout for that file.&lt;br /&gt;
&lt;br /&gt;
Finally, load the layout it built and clean up anything if you want, and consider adding better descriptions for each of the fields that you know (as it will use the variable names for both the subscripts AND descriptions in the layout).&lt;br /&gt;
&lt;br /&gt;
When you&#039;re all done, click &amp;quot;Done&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Functions to Generate Layouts from your code ====&lt;br /&gt;
&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnGenerateLayout]]&lt;br /&gt;
* [[#fnGenerateLayout_.26_fnWriteLayout|fnWriteLayout]]&lt;br /&gt;
&lt;br /&gt;
=== Code Templates ===&lt;br /&gt;
&lt;br /&gt;
One more tool FileIO has to help is the ability to generate code based on your file layouts.&lt;br /&gt;
&lt;br /&gt;
Run FileIO and this time select a file layout and press &amp;quot;Generate Code&amp;quot;. A window will pop up listing all the &amp;quot;Code Templates&amp;quot; that are available. Select one (and then select a key if it asks you - some templates require extra info). It looks like nothing happened, but the code you generated is now in your clip board. Paste it somewhere and take a look at it!&lt;br /&gt;
&lt;br /&gt;
Code Templates help us to standardize our ways of doing things, which eventually leads to more and more powerful tools we can use. Code Templates also help ensure we use cleaner code by doing some of the busy-work of writing clean code for us.&lt;br /&gt;
&lt;br /&gt;
==== Writing Code Templates ====&lt;br /&gt;
FileIO ships with several basic code templates. If you want to add your own, take a look in the filelay\templates folder (configurable in fileio.ini) and look at basic.brs. Copy it to your own program and then modify it to have your own code templates in it. Write me at gabriel.bakker@gmail.com with any questions. I want to help.&lt;br /&gt;
&lt;br /&gt;
And if you write good code templates, its easy to share them with the BR community. Anyone can download your templates and place them in this folder and they&#039;ll instantly be available for use.&lt;br /&gt;
&lt;br /&gt;
== FileIO Function Reference ==&lt;br /&gt;
&#039;&#039;The FileIO Library contains a number of other useful functions.&#039;&#039; The following functions are available and you are welcome to use them:&lt;br /&gt;
&lt;br /&gt;
=== Primary Functions ===&lt;br /&gt;
&lt;br /&gt;
==== fnOpen ====&lt;br /&gt;
fnOpen is the primary function that opens a file, and it’s used like so:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def Fnopen(Filename$*255, Mat F$, Mat F, Mat Form$; Inputonly, Keynum, DontSortSubs, Path$*255, Mat Descr$, Mat FieldWidths, SupressPrompt, IgnoreErrors, SuppressLog)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that all of the parameters after MAT Form$ are optional. If you need to specify a parameter in the middle of the optional parameter group, just list zeroes and empty strings for the intervening unused parameters. &lt;br /&gt;
&lt;br /&gt;
*FileName$ - The filename of the file layout for the file you’re reading.&lt;br /&gt;
*MAT F$ - The array that will be used to hold the string data for the file.&lt;br /&gt;
*MAT F – The array that will hold the numeric data for the file.&lt;br /&gt;
*MAT Form$ - An array of form statements.&lt;br /&gt;
*InputOnly – 1 means open for input only. 0 means open for OutIn and open every key file associated with the given data file (this is because all key files need to be open for the keys to be updated on an output) (defaults to 0). Files opened for input process considerably faster than files opened for output. Furthermore when files are opened for input only, alternate key files are &#039;&#039;not&#039;&#039; opened.&lt;br /&gt;
*KeyNum – This tells the function of which key to return the file handle (defaults to 1). Specify -1 to force the file to open Relative, without using its keys. If a file has no keys, it will always be opened Relative.&lt;br /&gt;
*DontSortSubs – The function by default, when compiling the Form statement, will sort the string and numeric subs in order to allow for reading the data into an array, and this parameter would turn this functionality off. However, if you are trying to read your data without reading it into an array, you are missing out on some serious efficiencies. You should normally leave this option turned off (0). This is a totally unsupported feature, and should always be set to 0, if it is specified at all.&lt;br /&gt;
*Path$ - The path to your data files. This optional parameter can be passed in by the calling function. It is prefixed to the beginning of the paths given in the file layout files. It is useful when your data files can be in different locations depending on the state of your program.&lt;br /&gt;
*mat Description$ - This optional parameter provides a way to read the description for each field from the original file layout. If the parameter is not provided, no description data will be returned. &lt;br /&gt;
*mat Fieldwidths – This optional parameter will return an array of the calculated display field widths. This number will always be at least large enough to contain the data for this field.&lt;br /&gt;
*SuppressPrompt - This optional parameter can be used to suppress the prompt normally given when fileIO creates a file. It can have three values. A value of 0, the default, uses the setting stored in your FileIO fnSettings routine to determine weather or not to prompt on Creation of a new file. A nonzero value always suppresses the prompt. A value of 1 indicates never to create a file if the file doesn&#039;t exist. A value of 2 automatically creates the file if the file doesn&#039;t exist.&lt;br /&gt;
*IgnoreErrors - Normally when fileio opens a data file, if there are errors trying to open the file, it prints them out to the debug console and pauses so that you can try to find out whats going wrong. If you specify 1 for &amp;quot;IgnoreErrors&amp;quot; it will cause Fileio to log those errors instead, and continue loading your program. This will result in the fnOpen file function returning Zero instead of the opened file number, so if you use IgnoreErrors, you need to test to make sure that fnOpen returned a file number before you try to access the file.&lt;br /&gt;
*SuppressLog - this optional parameter can be used to suppress writing to the log file for this one specific open statement. I use it for files that will be opened all the time, for example, the menu data file on one of my customers systems. Without this boolean parameter, his log file is quickly filled up with useless information any time his employees look at his menu. I specify this optional parameter to suppress logging anything about the menu file so that I can more easily find the actual programs they&#039;ve used when looking in the logfile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note: When using fnOpenFile, you really want to call your local function fnOpen, which in turn calls fnOpenFile for you.&#039;&#039;&#039;&#039;&#039;  FnOpenFile is for internal use only.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  Let FFILE=FNOPEN(“FARM”,MAT FARM$,MAT FARM,MAT FORM$,0,2)&lt;br /&gt;
  Let RFILE=FNOPEN(“ROUTE”,MAT ROUTE$,MAT ROUTE,MAT FORM$,1,3)&lt;br /&gt;
  Let CFILE=FNOPEN(“CUSTOMER”,MAT CUSTOMER$,MAT CUSTOMER,MAT FORM$)&lt;br /&gt;
&lt;br /&gt;
assuming:&lt;br /&gt;
  farm.dat has 3 keys: farm.ky1, farm.ky2, farm.ky3&lt;br /&gt;
  route.dat has 4 keys: route.ky1 … route.ky4&lt;br /&gt;
  customer.dat has 2 keys: customer.ky1, customer.ky2&lt;br /&gt;
&lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Farm.Dat, kfname=farm.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Farm.Dat, kfname=farm.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Farm.Dat, kfname=farm.ky3”,internal,outin,keyed&lt;br /&gt;
  OPEN #4: “Name=Route.Dat, kfname=route.ky3”,internal,input,keyed&lt;br /&gt;
  OPEN #5: “Name=Customer.Dat,kfname=customer.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #6: “Name=Customer.Dat,kfname=customer.ky2”,internal,outin,keyed&lt;br /&gt;
  LET FFILE=1&lt;br /&gt;
  LET RFILE=4&lt;br /&gt;
  LET CFILE=5&lt;br /&gt;
&lt;br /&gt;
This will also resize the arrays, set the form statement, and create all the subscripts to make accessing the data clear and simple, as in the above example. The KEYNUM parameter is where you list the key file you would like to use for reading and writing. The extra keys are opened to ensure that all keys get updated properly when the file is changed.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
  DIM FORM$(1),PRICE$(1)*255,PRICE(1),&lt;br /&gt;
  DIM DESCRIPTION$(1)*80,FIELDWIDTHS(1)&lt;br /&gt;
&lt;br /&gt;
  Let PRICEFILE=FNOPEN(“PRICE”,MAT PRICE$,MAT PRICE,MAT FORM$,0,2,0,&amp;quot;&amp;quot;,MAT DESCRIPTION$)&lt;br /&gt;
&lt;br /&gt;
Assuming the Price File layout (filelay\price) contains:&lt;br /&gt;
&lt;br /&gt;
  price.dat, PR_, 1&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  DUMMY,          Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
  DESCRIPTION$,   Description of Price Rule,   C   30&lt;br /&gt;
 &lt;br /&gt;
This will perform the following opens and set the following variables:&lt;br /&gt;
&lt;br /&gt;
  OPEN #1: “Name=Price.Dat, kfname=Price.ky2”,internal,outin,keyed&lt;br /&gt;
  OPEN #2: “Name=Price.Dat, kfname=Price.ky1”,internal,outin,keyed&lt;br /&gt;
  OPEN #3: “Name=Price.Dat, kfname=Price.ky3”,internal,outin,keyed&lt;br /&gt;
  let PRICEFILE=1&lt;br /&gt;
  let PR_FARM=1&lt;br /&gt;
  let PR_ITEM=2&lt;br /&gt;
  let PR_GRADE=3&lt;br /&gt;
  let PR_DESCR=4&lt;br /&gt;
  let PR_PRICE=1&lt;br /&gt;
  let PR_COST=2&lt;br /&gt;
  let PR_XOPRICE=3&lt;br /&gt;
  let PR_XOCOST=4&lt;br /&gt;
  let PR_MOPRICE=5&lt;br /&gt;
  let PR_MOCOST=6&lt;br /&gt;
  let PR_VOPRICE=7&lt;br /&gt;
  let PR_VOCOST=8&lt;br /&gt;
  MAT FORM$(1)&lt;br /&gt;
  MAT PRICE$(4)&lt;br /&gt;
  MAT PRICE(8)&lt;br /&gt;
  MAT DESCRIPTION$(12)&lt;br /&gt;
  MAT FIELDWIDTHS(12)&lt;br /&gt;
  let FORM$(1)=CFORM$(”form C 4,C 4,C 4,POS 74,C 30,POS 50,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2,BH 3.2”)&lt;br /&gt;
  let Description$(1)= “Farm Code (or blank)”&lt;br /&gt;
  let Description$(2) = “Item Code”&lt;br /&gt;
  let Description$(3) = “Quality”&lt;br /&gt;
  let Description$(4) = “Description of Price Rule”&lt;br /&gt;
  let Description$(5) = “Default Price”&lt;br /&gt;
  let Description$(6) = “Default Cost”&lt;br /&gt;
  let Description$(7) = “Default Christmas Price”&lt;br /&gt;
  let Description$(8) = “Default Christmas Cost”&lt;br /&gt;
  let Description$(9) = “Default Mothers D Price”&lt;br /&gt;
  let Description$(10) = “Default Mothers D Cost”&lt;br /&gt;
  let Description$(11) = “Default Valentine Price”&lt;br /&gt;
  let Description$(12) = “Default Valentine Cost”&lt;br /&gt;
  let FieldWidths(1) = 4&lt;br /&gt;
  let FieldWidths(2) = 4&lt;br /&gt;
  let FieldWidths(3) = 4&lt;br /&gt;
  let FieldWidths(4) = 30&lt;br /&gt;
  let FieldWidths(5) = 8&lt;br /&gt;
  let FieldWidths(6) = 8&lt;br /&gt;
  let FieldWidths(7) = 8&lt;br /&gt;
  let FieldWidths(8) = 8&lt;br /&gt;
  let FieldWidths(9) = 8&lt;br /&gt;
  let FieldWidths(10) = 8&lt;br /&gt;
  let FieldWidths(11) = 8&lt;br /&gt;
  let FieldWidths(12) = 8&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
There are a few things I’d like to point out about the example above. First, you will notice that the form statement jumps around to put all the strings first, and then the numbers last. This is why it has a “POS 74, C 30,POS 50”. Then notice that the subscripts for the string variables are 1 .. 4, and the subscripts for the numbers are 1 .. 8. Finally, the order of the Description and FieldWidth fields also match the sorted positioning of the Form Statement.&lt;br /&gt;
&lt;br /&gt;
The reason that we sort our form statements is so that BR will allow us to read the file into arrays by saying:&lt;br /&gt;
&lt;br /&gt;
  Read #pricefile, using form$(pricefile) : mat price$, mat price&lt;br /&gt;
&lt;br /&gt;
Without resorting, the above statement would give a conversion error as BR attempted to put the PR_PRICE field inside mat price$(4). However, because we have reordered the form statement, we are able to read them safely into two arrays, and then we will be able to use the values without caring what position they are in the file, by simply saying PRICE$(PR_DESCRIPTION) when we want the description, and PRICE(PR_PRICE) when we want the pr_Price field.&lt;br /&gt;
&lt;br /&gt;
Another thing you may notice about the above example is that it gives the calculated display length for the numeric fields as 8, when on the disk (and in the file layout) they are listed as BH 3.2. The reason for this is the program looks at the BH 3, and figures that a number that takes up 3 bytes on disk has a potential maximum size of 256**3 or 16,777,216, and therefore will need up to 8 characters of screen display space to view. &lt;br /&gt;
&lt;br /&gt;
Finally, note that any field with a form statement of X is ignored by the library, except that the blank space is used when building the FORM statement.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseFile ====&lt;br /&gt;
This function will close the specified file number and all keys that were opened with the file.  The purpose of this function is to facilitate the closing of the extra keys that are opened when you open a file for OutIn with the library. There is no need to use this for files opened for input because it is easier to just close the file directly. Secondary keys are not opened by FileIO for files opened for input only. &lt;br /&gt;
&lt;br /&gt;
You must give the function the name of the file layout. It uses this information to determine how many keys were opened, and how many it must therefore close. &lt;br /&gt;
&lt;br /&gt;
Then it basically starts at the file number you gave it, and closes the next X files that were opened with the same file name as this, where X is the number of keys associated with this file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseFile(filenumber,filelay$;path$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileNumber – The filenumber of the file you are trying to close.&lt;br /&gt;
*FileLay$ - The name of the file layout for this file. This is used to determine how many keys the file would have been opened with and therefore how many we now need to close.&lt;br /&gt;
*Path$ - Optional Alternate Path. Use to close files that were opened using the open file&#039;s optional alternate path parameter.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileNumber ====&lt;br /&gt;
FnGetFileNumber is a simple function to find a valid unused file handle. If you use this function in all of your programs, they will become much more portable, as you will never have to worry about your file handles fighting with each other. The function will return 0 if no free file number was found (but with 999 of them available now, I have never seen it happen).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGetFileNumber(;X,Count)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*X – This optional parameter will specify where to start looking.&lt;br /&gt;
*Count - This optional parameter will specify how many file numbers to find in a row. If count is 3, then fnGetFileNumber will return the first filenumber in the first gap of three unused file numbers that it finds.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Helper Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions perform various useful calculations to aid in interacting with data files when you&#039;re using fileio.&lt;br /&gt;
&lt;br /&gt;
==== fnBuildKey$ ====&lt;br /&gt;
This function will return the key for a given record in a data file. It actually reads the file layout and builds the key to match the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnBuildKey$(Layout$, Mat F$, Mat F; Keynum)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the name of the data file to build the key for.&lt;br /&gt;
*Mat F$ - the string array of the file object&lt;br /&gt;
*Mat F - the numeric array of the file object&lt;br /&gt;
*KeyNum - This optional parameter specifies which of the key files in the given layout the key should be built for.&lt;br /&gt;
&lt;br /&gt;
To use this, you want some code like in the following example:&lt;br /&gt;
&lt;br /&gt;
  mat customer$=(&amp;quot;&amp;quot;) : mat Customer=(0)&lt;br /&gt;
  let customer$(cu_name)=CustName$&lt;br /&gt;
  let customer$(cu_phone)=CustPhone$&lt;br /&gt;
  read #customer, using form$(Customer), key=fnBuildKey$(&amp;quot;customer&amp;quot;,mat Customer$,mat Customer,2) : mat Customer$, mat Customer nokey KeyNotFound&lt;br /&gt;
  ! The above code assumes that the second key of the Customer file is based on the Name and Phone fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By using this logic you are able to avoid directly specifying the key in your code - which means that even if the key changes in the future, as long as your code specifies enough information, it will still find the correct key. In our example, even if we dropped the phone number from the key in the future, or even if we expand the length of the Customer Name field, our code would still work and fnBuildKey$ would still build the correct key without us having to look at or change our code again.&lt;br /&gt;
&lt;br /&gt;
This kind of reasoning is central to the fileio system, which, when implemented properly, ensures that you can change your data files in any way you need to in the future, without breaking your existing programs.&lt;br /&gt;
&lt;br /&gt;
==== fnUniqueKey ====&lt;br /&gt;
This function tests to see if a given key is unique to the specified file. This function is very useful for situations where you need to ensure that the user-entered key is unique.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUniqueKey(Fl,key$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file number of the opened file.&lt;br /&gt;
*Key$ - This is the key to test. If this key is the key for a preexisting record in the file, the function will return false. If this key can not be found in the file, the function will return true.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeUniqueKey$ ====&lt;br /&gt;
This function generates a unique key for a given file. This is useful if you do not care what the key is, but it needs to be unique. I use this function in situations where the user never needs to know what the given key is. &lt;br /&gt;
&lt;br /&gt;
This function reads the key length for the given file. Then it returns a string that is the right length, which is generated by counting in binary until a key is found that does not appear in the file. The first key found for a 4 byte key field would be chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0). If that key was already in use in the file, the function would return chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(1). If that one was also in use, it would then try chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(0)&amp;amp;chr$(2), and so on until it found one that wasn’t in use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnMakeUniqueKey$(fl)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – This is the file number of the opened file for the desired key.&lt;br /&gt;
&lt;br /&gt;
==== fnKey$ ====&lt;br /&gt;
This function formats the key for the given file number. All it does is ensure the key is the correct length. You should use fnBuildKey$ instead to properly build the correct key for the record you want to read.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnKey$(FileNumber, Key$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileNumber - the file number we are formatting the key for&lt;br /&gt;
*Key$ - the key we are trying to format.&lt;br /&gt;
&lt;br /&gt;
==== fnNotInFile ====&lt;br /&gt;
This function will determine if a non-key element in a line is unique. It works almost exactly like fnUniqueKey specified above, except that it allows you to enter the subscript value of the element you are checking for uniqueness. You can use this to look for uniqueness in any field, not just in the key field. Additionally, the search engine used by this function looks for a partial match, and will only return true if the given string is not found anywhere in the specified element for the given file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnNotInFile(string$*100,filename$,sub)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - Value to check for uniqueness in this file.&lt;br /&gt;
*Filename$ - This is the name of the file layout of the file you want to check. This file does not have to be open in order for you to search it, because the function will open it for you.&lt;br /&gt;
*Sub – This is the subscript value of the element you are checking.&lt;br /&gt;
&lt;br /&gt;
==== fnSortKeys ====&lt;br /&gt;
This function takes an array of Primary Keys indicating records in the data file, and resorts it into the order you would have expected using one of the other keys for your data file.&lt;br /&gt;
&lt;br /&gt;
For example: Lets say you had a customer file, and the first key was Customer Code and the second key was Customer Last Name. This function would take an array of Customer Codes and sort it into Last Name order.&lt;br /&gt;
&lt;br /&gt;
This function requires the file to already be opened (which is usually the case when you have an array of keys from that file).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSortKeys(mat Keys$,Layout$,DataFile,mat F$,mat F,mat Form$;KeyNum)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Keys$ - the array of keys. These keys are based on whatever key the file was opened with in DataFile.&lt;br /&gt;
*Layout$ - the file layout for the file.&lt;br /&gt;
*DataFile - the open file number matching the key file that matches mat Keys$.&lt;br /&gt;
*mat F$ - array sized to hold the strings from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat F - array sized to hold the numerics from the data file. This is the array you get back from fnOpen.&lt;br /&gt;
*mat Form$ - array of form statements, that you get back from fnOpen.&lt;br /&gt;
*KeyNum - This is the key that we&#039;ll sort based on. If not given, the first key listed in the layout is assumed.&lt;br /&gt;
&lt;br /&gt;
=== File Reading Functions ===&lt;br /&gt;
&lt;br /&gt;
These functions actually read information from your data file and return it. These functions can be used to simplify the logic in your programs for many common tasks.&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllKeys ====&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record into (a) dynamically dimensioned array(s). For example, I could tell it to give me the Inventory Code for every inventory item in my database in one array, while placing the Inventory Description (name) for each item into the corresponding position in another array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadAllKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array&lt;br /&gt;
*mat out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  FnReadAllKeys(InventFile,mat Invent$,mat Invent,mat Form$,IN_Code,mat InvtList$,IN_Name,mat InvtNames$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadMatchingKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record where the key matches the specified key into (a) dynamically dimensioned array(s). This function is the same as the above function except this one will filter the results, returning only those records where the key matches the specified key.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadMatchingKeys(Fl,mat f$,mat f,mat fm$,key$,keysub,sub1,mat out1$;sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*mat F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*mat fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - Only records where the key field matches key$ will be returned.&lt;br /&gt;
*Keysub – This tells the function which element is the key element for this particular file number.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*mat out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnReadAllNewKeys ====&lt;br /&gt;
&lt;br /&gt;
This function will read through a file, returning the specified element(s) from each record that isn’t already there into (a) dynamically dimensioned array(s). This function is the same as the above function, except this one will filter the results returning only those records that don’t already exist in the array (eliminating duplicates).&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnReadAllNewKeys(Fl,mat f$,mat f,mat fm$,sub1,mat out1$; dont_reset,sub2,mat out2$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Dont_reset – By default the array will clear the contents of the arrays that are passed in. This Boolean flag will instruct the function to not empty the passed in arrays.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) parameter. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFilterKeys ====&lt;br /&gt;
&lt;br /&gt;
This function by Mikhail Zheleznov is a modification of the above functions. Like its predecessors, it reads an entire file, populating the specified arrays with data from all or some of the records in the file. The given subscripts specify which fields to return from each record. The key given specifies search criteria for the records. The filter specifies filtering criteria that can be preformed on the data before it is returned. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadFilterKeys(Fl,Mat F$,Mat F,Mat Fm$,Key$,Keyfld,Sub1,Mat Out1$;Filter$,Filter_Sub,Readlarger,Sub2,Mat Out2$, Sub3, Mat Out3$, Sub4,Mat Out4$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FL – The file handle for the already opened file in question.&lt;br /&gt;
*MAT F$ - Work array with a size that corresponds to the number of string elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT F – Work array with a size that corresponds to the number of numeric elements in the record (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*MAT fm$ - Array of Forms statement. FM$(FL) must be the form statement for this file (this is calculated automatically in the Open statement, if you used this library to open the file).&lt;br /&gt;
*Key$ - The key to search for in the read statements&lt;br /&gt;
*Keyfld – the subscript of the key field in the data file. This must match the key field specified in the data file otherwise the function won’t work.&lt;br /&gt;
*Sub1 – Subscript of the element to return in the first array.&lt;br /&gt;
*MAT out1$ - Array in which to return the data. This array will be resized to match the number of records in the file.&lt;br /&gt;
*Filter$ – This optional parameter identifies matches to search for in the records. It works in conjunction with Filter_Sub&lt;br /&gt;
*Filter_Sub – This parameter specifies which field to look for in the records for matches to Filter$. Only records which have Filter$ in the specified field will be returned.&lt;br /&gt;
*Sub2 – Subscript of the element to return in the second array. This is optional. If it is specified, you must give MAT out2$, or BR will return an error.&lt;br /&gt;
*MAT out2$ - Array in which to return the second (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub2 is given (non-zero). This parameter will not be used if Sub2 is not given or is given as 0.&lt;br /&gt;
*Sub3 – Subscript of the element to return in the third array. This is optional. If it is specified, you must give MAT out3$, or BR will return an error.&lt;br /&gt;
*MAT out3$ - Array in which to return the third (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub3 is given (non-zero). This parameter will not be used if Sub3 is not given or is given as 0.&lt;br /&gt;
*Sub4 – Subscript of the element to return in the fourth array. This is optional. If it is specified, you must give MAT out4$, or BR will return an error.&lt;br /&gt;
*mat out4$ - Array in which to return the fourth (optional) field. This array will be resized to match the number of records in the file. This parameter MUST be provided when Sub4 is given (non-zero). This parameter will not be used if Sub4 is not given or is given as 0.&lt;br /&gt;
&lt;br /&gt;
This function was written by Mikhail Zheleznov for the fileIO library.&lt;br /&gt;
&lt;br /&gt;
==== fnReadDescription$ ====&lt;br /&gt;
&#039;&#039;fnReadDescription$(Fl,Subscript,key$,mat F$,mat F,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout) (RT_NAME, which should equal 2 after opening routefile (unless we change the file layout later)).&lt;br /&gt;
*key$ - Item that should match a key in this file somewhere (in this case it is the route code from the farm record).&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading they have to be dimensioned properly, which is why we use our colorcat$ and our colorcat for these parameters.&lt;br /&gt;
*mat Form$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
In the example program I used above, I am reading the Color File to get details about each color. The color file contains a colorcat code field, but I want to display the colorcat description. The colorcat file contains a few details about a color category, and it is indexed based on colorcat code:&lt;br /&gt;
&lt;br /&gt;
  route.dat, RT_&lt;br /&gt;
  route.key, CODE&lt;br /&gt;
  recl=512&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE,           Routing Code,                C    6&lt;br /&gt;
  NAME,           Routing Name Description,    C   30&lt;br /&gt;
  MOFT,           T/A/C (truck/air/courier),   C    1&lt;br /&gt;
  SHIPPINGDAY,    Day of Week for Shipping,    C    7&lt;br /&gt;
&lt;br /&gt;
Now, as you know, in the above example, we have already opened the ColorCat File, and we have opened and read a Color record.&lt;br /&gt;
 &lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  30120       read #colorfile, using form$(colorfile) : mat color$, mat color eof Ignore&lt;br /&gt;
  30180       LET ccode$=color$(CO_CODE) !:&lt;br /&gt;
              LET Name$=color$(CO_NAME)&lt;br /&gt;
&lt;br /&gt;
Now all that’s left to do is to take the Color File’s ColorCat code and use it to look up the ColorCat File’s description.&lt;br /&gt;
&lt;br /&gt;
  30190 LET ColorCat$ = fnReadDescription$(ColorCatFile, CC_NAME, Color$(CO_CATEGORY),&lt;br /&gt;
    mat ColorCat$, mat ColorCat, mat form$)&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedDescription$ ====&lt;br /&gt;
This function accomplishes the same thing as fnReadDescription except that it opens the data file for you. It is slower then FnReadDescription$, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadUnopenedDescription$(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the value of the key field in the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2, so sometimes it is a good idea to design your data files so that string field number two is a descriptive field, like Name or Description.&lt;br /&gt;
&lt;br /&gt;
This function only works on the first key file for each data file. This function is a convenience function but it is slow because it opens the file and closes it for each call. Opening a file is one of the most time consuming program operations.&lt;br /&gt;
&lt;br /&gt;
==== fnReadNumber ====&lt;br /&gt;
&lt;br /&gt;
fnReadNumber does the same thing that ReadDescription does except it reads a numeric field instead of a description.&lt;br /&gt;
&lt;br /&gt;
Lets take a look at how this function works:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadNumber(Fl,subscript,key$,mat F$,mat F,mat fm$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Fl – File Handle for file to use (Route File).&lt;br /&gt;
*Subscript – Index of field in record to use (should be taken from file layout).&lt;br /&gt;
*Key$ - Item that should match a key in this file somewhere.&lt;br /&gt;
*mat F$ &amp;amp; mat F - Work Variables to hold a file record from the file we are reading. They have to be dimensioned properly by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat Fm$ - This will need to be our array of form statements, so the function can read the file properly.&lt;br /&gt;
&lt;br /&gt;
==== fnReadUnopenedNumber ====&lt;br /&gt;
This function accomplishes the same thing as fnReadNumber except that it opens the data file for you. It is slower then FnReadNumber, but it is easier to use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadUnopenedNumber(Layout$,key$*255;Field)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are reading&lt;br /&gt;
*key$ - the key of the record to be read&lt;br /&gt;
*Field - the element of the data file to return. If not given, this defaults to 2.&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeDescription$ ====&lt;br /&gt;
This function is the same as fnReadDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeDescription$(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat F,mat form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedDescription$ ====&lt;br /&gt;
This is the same as ReadUnopenedDescription$ but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedDescription(Layout$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelativeNumber ====&lt;br /&gt;
This is the same as fnReadNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelativeNumber(FileNumber,SubscriptToRead,RecordNumber,mat F$,mat f,mat Form$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Filenumber - The file number of the open data file&lt;br /&gt;
*SubscriptToRead - The subscript to read&lt;br /&gt;
*RecordNumber - the Record number to read&lt;br /&gt;
*mat F$ and mat F - the arrays to hold the data. They must match the dimensions and should be set with [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
*mat form$ - the forms array is also set by [[FileIO_Library#fnOpenFile|fnOpen]].&lt;br /&gt;
&lt;br /&gt;
==== fnReadRelUnopenedNumber ====&lt;br /&gt;
This is the same as fnReadUnopenedNumber but for Relative Files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadRelUnopenedNumber(LayoutName$,RecordNumber;Field)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*RecordNumber - the record to read&lt;br /&gt;
*Field - the field subscript to read&lt;br /&gt;
&lt;br /&gt;
==== fnReadRecordWhere$ ====&lt;br /&gt;
This function returns the record where the given element matches the given value. It does not depend on any key files, and it can be used to locate a record based on any element. However, it loops through the entire data file to do this and it could be a bit slow for large data files. This function opens the file for you, so the file does not have to be already opened.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadRecordWhere$(Layout$,SearchSub,SearchKey$*255,ReturnSub)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Layout$ - the layout of the file we are searching&lt;br /&gt;
*SearchSub - Subscript of the element to look in&lt;br /&gt;
*SearchKey$ - The value we are looking for&lt;br /&gt;
*ReturnSub - Subscript of the element to return&lt;br /&gt;
&lt;br /&gt;
=== FileIO Utility Functions ===&lt;br /&gt;
==== fnDataCrawler ====&lt;br /&gt;
This function launches the Data Crawler as a function, so you can make your own programs link to it. Special thanks to Mikhail Zheleznov for turning the DataCrawler into a library function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDataEdit ====&lt;br /&gt;
This function is the same as fnDataCrawler only it opens a Grid for editing.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDataCrawler(Layout$;SRow$,SCol$,Rows$,Cols$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnShowData ====&lt;br /&gt;
&lt;br /&gt;
This function builds a list or a grid in a floating window that is tied to a data file. It uses the same function that the datacrawler uses, but its much more customizable. All other datacrawler functions are just wrappers for this one.&lt;br /&gt;
&lt;br /&gt;
The first parameter is the only required parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;def library fnShowData(FileLay$;Edit,sRow,sCol,Rows,Cols,KeyNumber,Caption$*127,Path$*255,KeyMatch$*255,SearchMatch$*255,CallingProgram$*255,mat Records,mat IncludeCols$,mat IncludeUI$,mat ColumnDescription$,mat ColumnWidths,mat ColumnForms$,DisplayField$*80,mat FilterFields$,mat FilterForm$,mat FilterCompare$,mat FilterCaption$,mat FilterDefaults$,mat FilterKey)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLay$ - the file layout you want to display&lt;br /&gt;
*Edit - 1 for Edit (Grid) and 0 for View (Listview)&lt;br /&gt;
*sRow, sCol - the start position. if not given, its centered. If given but out of bounds, they&#039;ll be automatically adjusted to fit the grid on the screen.&lt;br /&gt;
*Rows, Cols - the size. If not given, defaults to fullscreen. If given but not big enough to fit the UI elements you request, they&#039;re automatically adjusted to fit everthing.&lt;br /&gt;
*KeyNumber - the Key to use when reading the data, used with other parameters. If not given, the file is read Relative for faster performance. Required if KeyMatch$ is given.&lt;br /&gt;
*Caption$ - the Caption to display, defaults to FileIO&#039;s Datacrawler&lt;br /&gt;
*Path$ - Alternate path to use for data files (Prepended to the name found in the layout)&lt;br /&gt;
*KeyMatch$ - Show only records that match this key. You can use Partial Keys. If this parameter is given, you must also give KeyNumber (above).&lt;br /&gt;
*SearchMatch$ - Show only records that contain this substring in them anywhere&lt;br /&gt;
*CallingProgram$ - used for logging purposes, pass in the system function Program$&lt;br /&gt;
*mat Records - Show only these records in the Grid. Use it to control exactly what the user sees.&lt;br /&gt;
*mat IncludeCols$ - Show only these columns. These column names must match the subscript names from the file layout.&lt;br /&gt;
*mat IncludeUI$ - Which UI Options to show. Build this array with one element for each optional UI element to include. Explanations of UI elements follow:&lt;br /&gt;
**&amp;quot;ColumnsButton&amp;quot; - adds a Select Columns button that the user can use to modify which columns appear on the list.&lt;br /&gt;
**&amp;quot;ExportButton&amp;quot; - adds an Export to CSV button that exports the data to a CSV file.&lt;br /&gt;
**&amp;quot;ImportButton&amp;quot; - adds an Import from CSV button that imports from the CSV file.&lt;br /&gt;
**&amp;quot;AddButton&amp;quot; - adds a button that can be used to add records to the data file.&lt;br /&gt;
**&amp;quot;SaveButton&amp;quot; - adds a button allowing the user to save changes&lt;br /&gt;
**&amp;quot;DeleteButton&amp;quot; - adds a button allowing the user to delete a row&lt;br /&gt;
**&amp;quot;KeyButton&amp;quot; - adds a button allowing the user to jump to a specified position by key or by record number (depending on if the file is opened keyed or not)&lt;br /&gt;
**&amp;quot;QuitButton&amp;quot; - adds a Quit button to the screen (the Esc key also quits)&lt;br /&gt;
**&amp;quot;Search&amp;quot; - adds a case insensitive search box to the screen.&lt;br /&gt;
**&amp;quot;Border&amp;quot; - adds a border around the screen&lt;br /&gt;
**&amp;quot;Caption&amp;quot; - adds a caption. (If you specify a caption, this is automatically turned on. If you don&#039;t specify a caption but turn on caption here, then FileIO uses the default FileIO data crawler caption.&lt;br /&gt;
**&amp;quot;Recl&amp;quot; - adds the Recl to the caption&lt;br /&gt;
**&amp;quot;Position&amp;quot; - adds the positions to the field descriptions in the column headings.&lt;br /&gt;
*mat ColumnDescription$ - Show these captions. If blank, use the descriptions from the layout&lt;br /&gt;
*mat ColumnWidths - Use these widths for the data, if not given, calculate from the file layout&lt;br /&gt;
*mat ColumnForms$ - Display Format for the data, including DATE and FMT and PIC&lt;br /&gt;
*mat DisplayField$ - Counter Field to display on the Loading Window. If its a field with an associated DATE ColumnForms$ value, that column form will be used when displaying the Counter Field. It can be any of the following:&lt;br /&gt;
**REC - this will display the current &amp;quot;REC/LREC&amp;quot; data in the loading window.&lt;br /&gt;
**READCOUNT - this will display the &amp;quot;Record Count / LREC&amp;quot; in the loading window.&lt;br /&gt;
**FINDCOUNT - this will display the number of records that match the current search criteria by itself in the loading window.&lt;br /&gt;
**A Field Name - one of your field names will display that field value in the loading window&lt;br /&gt;
**A string literal - anything else will display as a string in the loading window, so you could put something like &amp;quot;Loading, please wait&amp;quot; here and it will display.&lt;br /&gt;
*mat FilterFields$ - Contains the subscript of the field to compare to&lt;br /&gt;
*mat FilterForm$ - Contains the format of the field to compare to&lt;br /&gt;
*mat FilterCompare$ - Contains the comparison type (&amp;quot;&amp;lt;&amp;gt;&amp;quot; or &amp;quot;&amp;gt;&amp;quot; or &amp;quot;=&amp;quot; or &amp;quot;&amp;gt;=&amp;quot; or &amp;quot;*&amp;quot;) (* means do a substring search)&lt;br /&gt;
*mat FilterCaption$ - The caption for the filter box&lt;br /&gt;
*mat FilterDefaults$ - The default value for the filter information&lt;br /&gt;
*mat FilterKey - The action to use when filtering the data this way (0 means simple exclude, 1 means start here, -1 means stop here)&lt;br /&gt;
**The final five arrays can be used to add user filter boxes to the data grid. You need one element in each array for every custom user filter box on the screen.&lt;br /&gt;
&lt;br /&gt;
==== fnBeginAudit ====&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|FileIO Compare]] routine is available and can be called from within your programs. To do so, call fnBeginAudit to mark the Start of the compare, then do whatever you need, then run fnCompare to compare everything that has changed between when you ran fnBeginAudit and when you ran fnCompare.&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnBeginAudit|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCompare ====&lt;br /&gt;
&lt;br /&gt;
See [[AuditBR#fnCompare|the documentation]] for more details.&lt;br /&gt;
&lt;br /&gt;
==== fnCSVImport ====&lt;br /&gt;
&lt;br /&gt;
This function calls the FileIO Import routine, the same one that you get from the Datacrawlers Import Button.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvImport(Layout$*64;SuppressDialog,FileName$*300,ImportModeKey)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout to import into&lt;br /&gt;
*SupressDialog - 1 to Supress the Import Dialog&lt;br /&gt;
*FileName$ - the name of the CSV file (Required if Dialog is Suppressed)&lt;br /&gt;
*ImportModeKey - the Import Mode Key (Required if Dialog is Suppressed)&lt;br /&gt;
**-1 - Add all records to the file&lt;br /&gt;
**0 - Update by Record Number (The file must contain a RecNum column and it must be first)&lt;br /&gt;
**1+ - Any positive number means Update by that Key (as listed in the layout).&lt;br /&gt;
&lt;br /&gt;
==== fnCSVExport ====&lt;br /&gt;
&lt;br /&gt;
This function exports a data file to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCsvExport(Layout$*64;SuppressDialog,Filename$*300,IncludeRecNums,KeyNumber,StartKey$,KeyMatch$,Startrec,mat Records,SearchMatch$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The File Layout to Export&lt;br /&gt;
*SuppressDialog - (1 to Suppress the Export Dialog, 0 to show it)&lt;br /&gt;
*Filename$ - the output file name (Required if SuppressDialog is 1)&lt;br /&gt;
*IncludeRecNums - Include a column with the Record Numbers in it&lt;br /&gt;
*KeyNumber - Use this key when reading the data for export&lt;br /&gt;
*StartKey$ - Start with the record that matches StartKey$&lt;br /&gt;
*KeyMatch$ - Export only the record(s) that match KeyMatch$&lt;br /&gt;
*StartRec - Start with this Record Number&lt;br /&gt;
*mat Records - Export only these records&lt;br /&gt;
*SearchMatch$ - Export only records containing this search string anywhere in them&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnExportListViewCsv ====&lt;br /&gt;
&lt;br /&gt;
Exports the contents of the specified Listview in CSV format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnExportListViewCsv(Window,Spec$;GenFileName,Delim$,FileName$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Window - The Window containing the listview.&lt;br /&gt;
*Spec$ - The Spec identifying the listview.&lt;br /&gt;
*GenFileName - Flag telling weather or not to generate the file name.&lt;br /&gt;
*Delim$ - Delimiter to use. Comma is default.&lt;br /&gt;
*Filename$ - Filename to use&lt;br /&gt;
&lt;br /&gt;
==== fnGenerateLayout &amp;amp; fnWriteLayout ====&lt;br /&gt;
&lt;br /&gt;
This function calls on the Generate File Layout Wizard to generate and save the layout indicated by the parameters you give it. You must give it the same valid parameters that you would have come up with if you worked through the layout wizard the normal way, by running FileIO directly and choosing &amp;quot;Generate Layout&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
You can also bypass the automatic parsing and generate a layout by specifying all the data directly and using fnWriteLayout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnGenerateLayout(mat OpenStrings$, ReadString$*999, FormString$*20000, DimString$*999; DisplayFile)&#039;&#039;&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat OpenString$ - array of all the open strings for the given file from one of your old programs.&lt;br /&gt;
*mat ReadString$ - solid read statement from one of your old programs reading the entire record, (or at least everything you want in the layout).&lt;br /&gt;
*mat FormString$ - the form statement that goes with the ReadString$ (practice using the Generate Layout Wizard the normal way for details)&lt;br /&gt;
*mat DimString$ - the string containing Dim statements for each array mentioned in ReadString$. We need this to know how big the arrays are.&lt;br /&gt;
*DisplayFile - if True (1), it will Display the new layout in notepad when its done creating it. If false (0), it will simply create the file.&lt;br /&gt;
&lt;br /&gt;
fnGenerateLayout takes all the above information and parses it into a layout, then calls fnWriteLayout to actually write the layout.&lt;br /&gt;
&lt;br /&gt;
If you already know the information you want to put in the layout, its more direct to call fnWriteLayout below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnWriteLayout(Name$,FileName$*127,VER,PRE$,MAT KFNAME$,MAT KDESCRIPTION$,MAT SUBS$,MAT DESCR$,MAT FORM$;RECL,MAT EXTRA$,DISPLAYFILE)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Name$ - the name of the new layout&lt;br /&gt;
*FileName$*127 - the name of the data file&lt;br /&gt;
*Ver - the version of the data file&lt;br /&gt;
*Pre$ - the prefix to use for the data file&lt;br /&gt;
*mat KfName$ - array of all the keys for the file&lt;br /&gt;
*mat Kdescription$ - array of the subscripts that each key is based on&lt;br /&gt;
*mat Subs$ - the subscript for each field in the file&lt;br /&gt;
*mat Descr$ - the descriptions for each field in the file&lt;br /&gt;
*mat Form$ - the form specs for each field in the file&lt;br /&gt;
*Recl - (optional) the record length of the file&lt;br /&gt;
*mat Extra$ - (optional) Additonal Information for each field in the file, placed in the Comments column of the new layout. This column is ignored by standard fileio processing.&lt;br /&gt;
DisplayFile - if True, open the file in Notepad after it has been created.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteStringCode ====&lt;br /&gt;
A Large Text Field that is searched. Code is run and any resulting variables are set, if they exist inside String enclosed between Substitute Chars (default []), then they will be replaced with the values derived by the code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteStringCode(&amp;amp;String$,Code$*2048;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*Code$ - code to be run. The resulting variables can be substituted into String&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnSubstituteString ====&lt;br /&gt;
A Large Text Field that is searched. Any matching fields in the passed in record will be substituted into their places. For example, if the string contained [cu_name] and you ran substitutions on the Customer file, then [cu_name] would be replaced by the name in the actual Customer record.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSubstituteString(&amp;amp;String$,Filelayout$,mat F$,mat F;SubstituteChar$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the string to be searched&lt;br /&gt;
*FileLayout$ - the layout to be searched&lt;br /&gt;
*mat F$ - the record to be used for substitution&lt;br /&gt;
*mat F - the record to be used for substitution&lt;br /&gt;
*SubstituteChar$ - Defaults to [] but here you can override the Substitute Character to be used.&lt;br /&gt;
&lt;br /&gt;
==== fnShowMessage ====&lt;br /&gt;
Displays a message to the user, in a child window. Returns the child window number, so that you can close it when you&#039;re done with the message.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;WindowNumber=fnShowMessage(Message$*54)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Message - the Message to display to the user&lt;br /&gt;
&lt;br /&gt;
=== File System Utility Functions ===&lt;br /&gt;
These functions perform other tasks that aid with interacting with the file system.&lt;br /&gt;
&lt;br /&gt;
==== fnGetFileDateTime$ ====&lt;br /&gt;
This does a directory listing to determine the Last Modified Date and Time of the given file. You pass it a file name, not a file layout, and it can be used on any file. Its not limited to BR internal files.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FileDate$=fnGetFileDateTime$(Filename$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnProgressBar ====&lt;br /&gt;
There&#039;s a Progress Bar that FileIO uses when copying or updating data files. You can use it in your own functions by calling fnProgressBar inside the main loop of the process that you want to display the progress bar over, and by calling fnCloseBar when you&#039;re done.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnProgressBar(Percent;Color$,ProgressAfter,ProgressThreshhold,UpdateThreshhold,Caption$*255,MessageRow$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Percent - Percentage Complete expressed as a float between 0 and 1&lt;br /&gt;
*Color$ - HTML Color Code of BR Color Attribute to color the Progress Bar. Defaults to Green.&lt;br /&gt;
*ProgressAfter - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*ProgressThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*UpdateThreshhold - This value can be used to prevent the progress bar from loading when the job takes only a short time. &lt;br /&gt;
*Caption$ - Optional message to display above the progress bar.&lt;br /&gt;
*MessageRow$ - Optional message to display on the progress bar.&lt;br /&gt;
&lt;br /&gt;
==== fnCloseBar ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCloseBar&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Call this function to close the progress bar window when your task is done running.&lt;br /&gt;
&lt;br /&gt;
==== fnCopyFile ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyFile(FromFile$*255,ToFile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FromFile$ - The file to copy.&lt;br /&gt;
*ToFile$ - The destination file name including path.&lt;br /&gt;
&lt;br /&gt;
This function copies any file, by opening it as an external file and reading and writing the data to the destination file. The advantage of this technique, over using the BR copy command, is this routine is more reliable when run on client server where you may be transferring large files from one side to the other over the internet.&lt;br /&gt;
&lt;br /&gt;
This fnCopyFile also has a progress bar that displays as the file is transferred, so the user knows your software is busy.&lt;br /&gt;
&lt;br /&gt;
This function works over Client Server. Under Client Server, specify @: at the beginning of your filename, to specify that the file is on the client. If the file is on the server, simply leave the @: off. See the chapter on [[Copy#Client_Server|using BR&#039;s copy command under Client Server]] for more details on the &amp;quot;@:&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This function&#039;s parameters and syntax are modeled off of the built in BR copy command, and like that one, this should work for local transfers, LAN transfers, or even internet transfers. This function will work better for internet transfers then the built in BR Copy Command, however. &lt;br /&gt;
&lt;br /&gt;
==== fnCopyDataFiles ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCopyDataFiles(DestinationFolder$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*DestinationFolder$ - the folder to copy your data files to.&lt;br /&gt;
&lt;br /&gt;
This function reads your file layouts and makes a copy of every data file (and key file) in your system to the destination folder, utilizing fnCopyFile above, for better performance and reliability when running over the internet, as well as a progress bar for each individual file.&lt;br /&gt;
&lt;br /&gt;
It also displays a listview while its copying so you can watch the progress while its transferring your data files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This can be used for making backups of your BR data, or for transferring the latest data onto a laptop for access to it while you&#039;re on the road away from the internet.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndex ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes a single data file&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndex(DataFile$*255;CallingProgram$*255,IndexNum,Path$*25)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Datafile$ - the file layout of the file to index&lt;br /&gt;
*CallingProgram$ - used for logging, pass Program$ in here&lt;br /&gt;
*IndexNum - The index to rebuild - if not given, rebuilds all indexes&lt;br /&gt;
*Path$ - the optional alternate path to use for rebuilding the data file.&lt;br /&gt;
&lt;br /&gt;
==== fnReIndexAllFiles ====&lt;br /&gt;
&lt;br /&gt;
This function reindexes all your data files. It doesn&#039;t require any parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReIndexAllFiles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnUpdateFile ====&lt;br /&gt;
This function checks and Updates a data file if it needs to be updated, by opening and then closing the data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnUpdateFile(FileLayout$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*FileLayout$ - The name of the file to update&lt;br /&gt;
&lt;br /&gt;
==== fnRemoveDeletes ====&lt;br /&gt;
&lt;br /&gt;
This function, written by Susan Smith, removes deleted records by copying the file with the -D option.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnRemoveDeletes(LayoutName$*255;Path$*255,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*LayoutName$ - the file layout&lt;br /&gt;
*Path$ - Optional Alternate Path&lt;br /&gt;
*CallingPRogram$ - used for logging, pass Program$ in here&lt;br /&gt;
&lt;br /&gt;
=== Layout Interrogation ===&lt;br /&gt;
Layout interrogation functions are provided for convenience and to enable future dictionary changes without disrupting any programs that interrogate it.&lt;br /&gt;
&lt;br /&gt;
==== fnMakeSubProc ====&lt;br /&gt;
This function obtains the subscript assignments that were assigned by fnOpen according to the file layout. The fnMakeSubProc$ routine obtains the subscript assignments without actually opening the file. This is useful when a file record is passed as arrays into a library function in another program, or when you chained to another program and you need to know how to reach the data in the arrays without actually reopening the file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnMakeSubProc(filelay$;mat Subscripts$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*FileLay$ - The name of the file layout from which to read the subscripts.&lt;br /&gt;
*mat Subscripts$ - If you give this optional array, fnMakeSubProc passes the subscripts back in this array rather then in the subs.$$$ file. This is much faster and helps avoid sharing conflicts.&lt;br /&gt;
&lt;br /&gt;
When this routine returns, you can set the subscripts in your program by executing every element of the Subscripts$ array in a for/next loop.&lt;br /&gt;
&lt;br /&gt;
If you did not pass in a subscripts array, then the a procedure file named subs.$$$ is created. If you proc that file, the proper subscripts will be set.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutArrays ====&lt;br /&gt;
This function reads the fields in a file layout into arrays. You call it like the following&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutArrays(filelay$,&amp;amp;prefix$;mat SSubs$, mat NSubs$, mat SSpec$, mat NSpec$,mat SDescription$, mat NDescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*filelay$ - the name of the layout to read&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
This function call would read the fields from the layout in filelay$, and it would return the prefix to prefix$. The Subs would be returned in mat SSubs$ and mat NSubs$.&lt;br /&gt;
&lt;br /&gt;
The Spec statements are returned in mat SSpec$ and mat NSpec$ and the Descriptions are returned in mat SDescription$ and mat NDescription$. The start positions on disk of each element are returned in mat SPos and mat NPos.&lt;br /&gt;
&lt;br /&gt;
All the arrays are optional. If you don’t pass them the information they are supposed to record is simply not returned.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutHeader ====&lt;br /&gt;
This function reads the header for a given file layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLayoutHeader(Layoutname$*255;&amp;amp;Filename$,Mat Keys$,Mat KeyDescription$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
&lt;br /&gt;
==== fnReadEntireLayout ====&lt;br /&gt;
This function reads the entire layout by calling fnReadLayoutHeader and fnReadLayoutArrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadEntireLayout(Layoutname$*255;&amp;amp;Filename$,&amp;amp;Prefix$,Mat Keys$,Mat KeyDescription$,Mat Ssubs$,Mat Nsubs$,Mat Sspec$,Mat Nspec$,Mat Sdescription$,Mat Ndescription$,Mat Spos,Mat Npos)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*LayoutName$ - Name of the layout to read&lt;br /&gt;
*Filename$ - The file name read from the layout&lt;br /&gt;
*prefix$ - the return value for the prefix for the file&lt;br /&gt;
*Mat Keys$ - Array to be populated with the list of keys for the file&lt;br /&gt;
*Mat KeyDescription$ - Key Description String returned from the file&lt;br /&gt;
*mat SSubs$ - the return value for all the string subscripts in the layout&lt;br /&gt;
*mat NSubs$ - the return value for the numeric subscripts in the layout&lt;br /&gt;
*mat SSpec$ - the return value for the string fields specs&lt;br /&gt;
*mat NSpec$ - the return value for the numeric fields specs&lt;br /&gt;
*mat SDescription$ - the return value for the Descriptions of the String Specs&lt;br /&gt;
*mat NDescription$ - the return value for the Descriptions of the Numeric Specs&lt;br /&gt;
*mat SPos - the return value for the starting positions of the String Specs&lt;br /&gt;
*mat NPos - the return value for the starting positions of the Numeric Specs&lt;br /&gt;
&lt;br /&gt;
==== fnReadSubs ====&lt;br /&gt;
This function reads the subscripts from a layout into Subs arrays.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadSubs(Layout$,mat SSubs$,mat NSubs$,&amp;amp;Prefix$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - the file layout name&lt;br /&gt;
*mat SSubs$ - the String Subscript Names&lt;br /&gt;
*mat NSubs$ - the Number Subscript Names&lt;br /&gt;
*Prefix$ - the files Prefix&lt;br /&gt;
&lt;br /&gt;
==== fnReadKeyFiles ====&lt;br /&gt;
This function reads the list of key files from the layout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadKeyFiles(Layout$,mat Keys$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - The file layout&lt;br /&gt;
*mat Keys$ - output array, the keys are returned here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnLayoutExtension$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the layout extension being used.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayouts ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnReadLayouts(mat Dirlist$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all the file layouts that FileIO can find.&lt;br /&gt;
&lt;br /&gt;
==== fnDirVersionHistoryFiles ====&lt;br /&gt;
This function reads the file layout folder and returns all the applicable layouts in the passed in array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDirVersionHistoryFiles(Layout$,mat DirList$;BypassExtension$)&#039;&#039;&lt;br /&gt;
*Layout$ - Which layout to look up version history of&lt;br /&gt;
*Mat Dirlist$ - After running the function, mat Dirlist$ will contain a list of all previous versions from history of the requested layout.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLayoutPath$ ====&lt;br /&gt;
This function doesn&#039;t actually interrogate your layouts. Instead, it interrogates your settings and returns the path specified for your layout files. This would usually be &amp;quot;filelay\&amp;quot; but you can change it in [[#fnSettings|fileio.ini.]]&lt;br /&gt;
&lt;br /&gt;
It has no parameters.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let Path$=fnReadLayoutPath$&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnDoesLayoutExist ====&lt;br /&gt;
This function returns true if the given file layout exists. It returns false if the layout does not exist.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;FnDoesLayoutExist(layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Layout$ - Layout to test for&lt;br /&gt;
&lt;br /&gt;
==== fnReadForm$ (uncompiled) ====&lt;br /&gt;
Sometimes you need to know the original form statement that fileio is using to read your data file, most often when you&#039;re debugging your file layout, and you&#039;re getting some problem when reading the file. The form statement that fileio normally returns is compiled using CFORM$ to save space in the array, and to make the execution of your read statements faster. You can use this function to return the original form statement for the file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let FormStatement$=fnReadForm$*10000(FileLayout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Remember to dimension FormStatement to something huge! fnReadForm$ can return up to 10,000 characters.&lt;br /&gt;
&lt;br /&gt;
==== fnReadFormAndSubs ====&lt;br /&gt;
&lt;br /&gt;
This function does the same as above but it also reads the subscripts from the file into an array.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;let fnReadFormAndSubs(Layout$,mat Subs$,&amp;amp;ReadForm$,&amp;amp;StringSize,&amp;amp;NumberSize)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note that unlike most of our functions, all the above parameters are required.&lt;br /&gt;
&lt;br /&gt;
The layout is the layout you&#039;re interrogating. The Array will be populated with the subscripts after the function. The ReadForm$ variable will be filled with the form statement for the file. Remember to dimension it large enough. StringSize and NumberSize will contain the number of string fields and numeric fields in the file.&lt;br /&gt;
&lt;br /&gt;
Mat Subs$ will be returned, strings first, numbers last, matching the form statement that is returned. So Subs$(1) is the first string subscript and Subs$(StringSize+1) is the first Numeric subscript.&lt;br /&gt;
&lt;br /&gt;
==== fnClearLayoutCache ====&lt;br /&gt;
&lt;br /&gt;
fnClearLayoutCash (v2.3+) clears out the cache of layout name and data to get realtime Updates to File Layouts.&lt;br /&gt;
&lt;br /&gt;
=== Other Useful Functions (non-layout related) ===&lt;br /&gt;
&lt;br /&gt;
==== fnAskCombo ====&lt;br /&gt;
&lt;br /&gt;
Opens a window with a combo box and returns the users selection.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnAskCombo$*255(mat Description$;Caption$*60,Default$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Description$ - contains the combo box choices&lt;br /&gt;
*Caption$ - contains the optional caption for the window&lt;br /&gt;
*Default$ - the text of the item in mat Description$ that you want to be selected by default&lt;br /&gt;
&lt;br /&gt;
==== fnSendEmail ====&lt;br /&gt;
&lt;br /&gt;
This function sends an email and an optional attachment to the email address specified.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnSendEmail(Emailaddress$*255,Message$*10000;Subject$*255,Invoicefile$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EmailAddress$ - the Destination Email Address&lt;br /&gt;
*Message$ - the Message$ to send&lt;br /&gt;
*Subject$ - the subject&lt;br /&gt;
*InvoiceFile$ - the filename of a file to attach. This is the BR filename (the function automatically converts it to an OS Filename.)&lt;br /&gt;
&lt;br /&gt;
The function returns 1 when the email was sent successfully, or 0 if it wasn&#039;t sent successfully.&lt;br /&gt;
&lt;br /&gt;
In order to use this function, you must have the emailcfg file layout in your layouts folder and you must have a copy of SendEmail.exe which is free and can be downloaded from the internet. Both of these files are included with the latest update of fileio.&lt;br /&gt;
&lt;br /&gt;
You also have to configure fileio with the authentication information for your email server.&lt;br /&gt;
&lt;br /&gt;
To configure your email server, simply run FileIO to get the data crawler, then select the emailcfg file layout and press F5 to edit it. Add a row if the file is empty.&lt;br /&gt;
&lt;br /&gt;
Simply fill in the information the file is asking for in the appropriate fields and save the data, and then test sending an email to make sure it worked.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More information about sendEmail.exe is available from the author&#039;s product site here: [http://caspian.dotconf.net/menu/Software/SendEmail/ http://caspian.dotconf.net/menu/Software/SendEmail/]&lt;br /&gt;
&lt;br /&gt;
==== fnClientEnv$ ====&lt;br /&gt;
&lt;br /&gt;
This function returns the value of a Windows Environment Variable on the client under Client Server.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnClientEnv$*255(EnvKey$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*EnvKey$ is the name of the Windows Environment Variable to check on the client.&lt;br /&gt;
&lt;br /&gt;
The function returns the value of the entered environment variable.&lt;br /&gt;
&lt;br /&gt;
==== fnClientServer ====&lt;br /&gt;
&lt;br /&gt;
This function returns true if you&#039;re currently running in Client Server mode, and false if you&#039;re running in the old &amp;quot;native&amp;quot; mode.&lt;br /&gt;
&lt;br /&gt;
==== fnEmpty &amp;amp; fnEmptyS ====&lt;br /&gt;
These functions test if the passed in array is empty or not..&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmpty(mat Numeric)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnEmptyS(mat String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
  def fnSomeFunction(String$,mat Array$; mat OtherArray)&lt;br /&gt;
     if fnEmpty(mat OtherArray) then&lt;br /&gt;
        ! Arrays were empty.&lt;br /&gt;
     end if&lt;br /&gt;
  fnend&lt;br /&gt;
&lt;br /&gt;
==== fnReadScreenSize ====&lt;br /&gt;
This function reads the screen size of the given window (or window 0 if a window wasn&#039;t passed.) This is useful for centering a window on the screen, or for making sure window 0 is big enough for the child window you&#039;re trying to create.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadScreenSize(&amp;amp;Rows,&amp;amp;Cols;Window)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Rows &amp;amp; Cols - returns the screen size in rows and columns.&lt;br /&gt;
*Window - the window to interrogate. If not given, assumes [[Open_Window#Comments_and_Examples|Window 0]].&lt;br /&gt;
&lt;br /&gt;
==== fnBuildProcFile ====&lt;br /&gt;
This function builds a proc file to be executed later. This function and the next simplify the process of executing code in a proc file. It can even be used to spawn a new session of BR.&lt;br /&gt;
&lt;br /&gt;
To use it, call fnBuildProcFile one or more times and specify some lines of code to execute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildProcFile(Command$*255)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnRunProcFile ====&lt;br /&gt;
&lt;br /&gt;
This function spawns a new copy of BR and in it, runs the proc file that you&#039;ve previously built using fnBuildProcFile (above).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; fnRunProcFile(;NoWait) &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optional parameter &amp;quot;NoWait&amp;quot; causes the other session to spawn and run in a new thread. If you don&#039;t specify NoWait, the current session pauses and waits for the other session to close before proceeding.&lt;br /&gt;
&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;load &amp;quot;&amp;amp;Program2$)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;run&amp;quot;)&lt;br /&gt;
  let fnBuildProcFile(&amp;quot;system&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
  let fnRunProcFile(1)&lt;br /&gt;
&lt;br /&gt;
==== fnLog &amp;amp; fnErrLog ====&lt;br /&gt;
These next two functions are functions to aid in logging. When you call either of these two functions, you give them a string that you wish to log. They will open the FileIO Log File and add a record to the end of it containing some user information such as login_name and session$, and the string that you specified. FnErrLog does the same thing as fnLog except it also logs the Error Number and the Line.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnLog(string$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;FnErrLog(String$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*String$ - the Log Message&lt;br /&gt;
&lt;br /&gt;
==== fnLogArray ====&lt;br /&gt;
&lt;br /&gt;
This function logs the given arrays to the log file, logging each field in the arrays. Its useful for recording, for example, an entire data record that is about to be written to a data file.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogarray(mat F$,mat f;Message$*512,CallingProgram$*255)&#039;&#039;&lt;br /&gt;
The first two parameters, of course, are the array to log. The third parameter is an optional message to be recorded along with the array, and the fourth optional parameter is the CallingProgram$ to save in the log along with the message. (The CallingProgram$ parameter can usually be passed as the system function Program$)&lt;br /&gt;
&lt;br /&gt;
==== fnSetLogChanges ====&lt;br /&gt;
This function works in conjunction with the next function, and they&#039;re for recording just what changed in a data file. When the file is read, you call fnSetLogChanges and record the &amp;quot;before&amp;quot; picture of the file record.&lt;br /&gt;
&lt;br /&gt;
After changes have been made and saved back to disk, you can call fnLogChanges below to record the actual changes.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnSetLogChanges(mat F$,mat F)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== fnLogChanges ====&lt;br /&gt;
This function is the other half of the function above. It compares the given arrays with the ones set previously in the above function and checks for changes. Then it generates a log message recording information about just the items that changed.&lt;br /&gt;
&lt;br /&gt;
The syntax is:&lt;br /&gt;
&#039;&#039;fnLogChanges(mat F$,mat F;Message$*1024,CallingProgram$*255,Layout$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You need to pass in the same two arrays as before, but this time the modified versions of them.&lt;br /&gt;
&lt;br /&gt;
You can also optionally pass a 1024 byte log message to record with the log entry.&lt;br /&gt;
&lt;br /&gt;
CallingProgram$ again should be passed as the system internal function Program$.&lt;br /&gt;
&lt;br /&gt;
The final parameter allows the passing of a Layout$. If you pass a Layout$, that layout is read and the subscripts are used when making the log message of changes, so that its easier to read. If you pass a layout, it will identify the changed fields by name. If you don&#039;t it will identify those fields by relative position number.&lt;br /&gt;
&lt;br /&gt;
==== fnViewLogFile ====&lt;br /&gt;
All of the above functions store the information by default into a BR internal file defined in the filelay\logfile layout.&lt;br /&gt;
&lt;br /&gt;
The fnViewLog function uses fnShowData to call the Data Crawler to display the FileIO Log File in a searchable listview.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnViewLogFile(;ShowQuit,ShowColumns,ShowExport)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*ShowQuit - Show a quit button on the UI (if off, ESC will quit).&lt;br /&gt;
*ShowColumns - Include a button to allow the user to select which columns to view.&lt;br /&gt;
*ShowExport - Include a button to export the log file to CSV.&lt;br /&gt;
&lt;br /&gt;
==== fnReadLockedUsers ====&lt;br /&gt;
&lt;br /&gt;
This function returns a list of all users using the locked data file.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnReadLockedUsers(mat Users$)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*mat Users$ - the list of users is returned here&lt;br /&gt;
&lt;br /&gt;
==== fnDisplayLength ====&lt;br /&gt;
This function calculates the display length of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnDisplayLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec - the Form Spec to return the display length of.&lt;br /&gt;
&lt;br /&gt;
==== fnLength ====&lt;br /&gt;
This function calculates the length on disk of a field based on its form spec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnLength(Spec$)&#039;&#039;&lt;br /&gt;
  &lt;br /&gt;
*Spec$ - the Form Spec to return the length of.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== fnStandardTime$ ====&lt;br /&gt;
Converts Military Time to Standard Time. Returns Standard Time$&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnStandardTime$(MilitaryTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*MilitaryTime$ - Time in 24 hr format.&lt;br /&gt;
&lt;br /&gt;
==== fnMilitaryTime$ ====&lt;br /&gt;
&#039;&#039;fnMilitaryTime$(StandardTime$;Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*StandardTime$ - Time in AM/PM Format&lt;br /&gt;
&lt;br /&gt;
==== fnCalculateHours ====&lt;br /&gt;
Calculates the difference between 2 specified times.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnCalculateHours(TimeIn$,TOut$,DaysIN,DaysOut)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*TimeIn$ - From Time&lt;br /&gt;
*TimeOut$ - To Time&lt;br /&gt;
*DaysIn - Days Start&lt;br /&gt;
*DaysOut - Days End&lt;br /&gt;
&lt;br /&gt;
==== fnBuildTime$ ====&lt;br /&gt;
Builds a time in the format used by the above functions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnBuildTime$(H,M,S,P;Military,Seconds)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*H - Hours&lt;br /&gt;
*M - Minutes&lt;br /&gt;
*S - Seconds&lt;br /&gt;
*P - Boolean indicating AM/PM - 0 is AM, 1 is PM&lt;br /&gt;
*Military - Flag indicating weather desired result is in Military Time or not&lt;br /&gt;
*Seconds - Flag indicating weather to count seconds or ignore them. (1 means count seconds)&lt;br /&gt;
&lt;br /&gt;
==== fnParseTime ====&lt;br /&gt;
Takes a time and pulls out the values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fnParseTime(T$,&amp;amp;H,&amp;amp;M,&amp;amp;S,&amp;amp;P)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*T$ - the time to parse&lt;br /&gt;
*H - Return value for Hours&lt;br /&gt;
*M - Return value for Minutes&lt;br /&gt;
*S - Return value for Seconds&lt;br /&gt;
*P - Return Value for AM/PM&lt;br /&gt;
&lt;br /&gt;
== FileIO fnSettings (Global Settings Code) ==&lt;br /&gt;
&lt;br /&gt;
The FileIO library can be made to implement certain policies across the board.  These are established by creating a file called fileio.ini and typing statements like the following into it. This file is &amp;quot;PROCed&amp;quot;, so you don&#039;t want to put line numbers in it.&lt;br /&gt;
&lt;br /&gt;
Anything you don&#039;t specify in fileio.ini will take its default value, shown below. So you only need to specify the items that you want to be different.&lt;br /&gt;
&lt;br /&gt;
Note, for the boolean settings below, 1 denotes TRUE and 0 denotes FALSE.&lt;br /&gt;
&lt;br /&gt;
  let EnforceDupkeys=1                   ! Enforce that key number 1 is unique key&lt;br /&gt;
  let Defaultfilelayoutpath$=&amp;quot;filelay\&amp;quot;  ! Path To Your File Layouts&lt;br /&gt;
  let Promptonfilecreate=1               ! Ask User Before Creating New Files&lt;br /&gt;
  let Createlogfile=0                    ! Use Logging&lt;br /&gt;
  let StartFileNumber=1                  ! Set above 300 to avoid conflicts with legacy programs.&lt;br /&gt;
  let CheckIndex=0                       ! Automatically Verify Indexes (slow)&lt;br /&gt;
  let CompressColumns=0                  ! Shrink or Expand Columns in Data Crawler by Default&lt;br /&gt;
  let MaxColWidth=20                     ! Max Default Column Width in Data Crawler&lt;br /&gt;
  let LogLibrary$=&amp;quot;&amp;quot;                     ! Defaut Log Library, defaults to None&lt;br /&gt;
  let LogLayout$=&amp;quot;&amp;quot;                      ! Log file layout, defaults to Internal File&lt;br /&gt;
  let AnimateDatacrawler=1               ! Use ScreenIO Animation for Datacrawler&lt;br /&gt;
  let TemplatePath$=&amp;quot;filelay\template\&amp;quot;  ! Default Template Path&lt;br /&gt;
  let IgnoreLayouts$=&amp;quot;&amp;quot;                  ! List any Ignore Layouts here.&lt;br /&gt;
  let CloseFileSimple=0                  ! Use simple comparison for fnCloseFile&lt;br /&gt;
&lt;br /&gt;
==== EnforceDupkeys ====&lt;br /&gt;
&lt;br /&gt;
Turn on the EnforceDupkeys option to force that the first key listed in your file layouts is a unique key. FileIO will generate the standard BR error for Dupkeys when attempting to create indexes if it is not unique.&lt;br /&gt;
&lt;br /&gt;
==== DefaultFileLayoutPath$ ====&lt;br /&gt;
&lt;br /&gt;
Use the DefaultFileLayoutPath option to specify the path from your programs to your file layout folder. The default is &amp;quot;filelay\&amp;quot;. Use this setting if you want to place your file layouts in another folder from the default.&lt;br /&gt;
&lt;br /&gt;
==== PromptOnFileCreate ====&lt;br /&gt;
&lt;br /&gt;
Use the PromptOnFileCreate setting to cause FileIO to display a message box whenever it is attempting to create a new file. This happens during the automatic update procedure, as well as during any attempt to access a file that does not exist. This setting should be turned off in your live system, and only turned on for development purposes. If you use the message box to cancel creating of the new file, then the file is not created, and fileIO or your application will most likely give an error when you attempt to actually access the new file.&lt;br /&gt;
&lt;br /&gt;
It can be useful during development to make sure that you don&#039;t accidentally create the wrong files, due to an incorrectly specified path or a typeo in the filename in the layout file. However, in a live system, these things have already been tested, and you generally want the default behavior, because you don&#039;t want your users to have the ability to cancel the normal operation of FileIO.&lt;br /&gt;
&lt;br /&gt;
==== CreateLogFile ====&lt;br /&gt;
&lt;br /&gt;
Use this option to specify weather or not to use the FileIO log file. If it is turned on, a log file called FileIO.log in the current directory is created with entries listing all attempts to open a file, automatically update one, or automatically update your indexes.&lt;br /&gt;
&lt;br /&gt;
==== StartFileNumber ====&lt;br /&gt;
&lt;br /&gt;
Use this option to set the starting file number that the fnGetFileNumber and the fnOpen functions use to search for available file numbers. This is so that if you have certian reserved file numbers in your code, you can make sure that FileIO will not use those reserved file numbers, causing a conflict with your already existing programs. The default is 1, which is fine if your software does not have any reserved file numbers.&lt;br /&gt;
&lt;br /&gt;
The allowable BR file numbers are 1-200 and 300-999.&lt;br /&gt;
&lt;br /&gt;
==== CheckIndex ====&lt;br /&gt;
&lt;br /&gt;
This function is used for Partial FileIO Implementations. If all of your software uses FileIO then all of your indexes are kept automatically up to date by the FileIO library and BR&#039;s internal file processing systems. However, if some of your programs do not use FileIO, and you use the FileIO library to add a new index file that those other programs do not know about, then its possible for the additional index file to become out of date when the master file is updated by your other programs.&lt;br /&gt;
&lt;br /&gt;
Use this setting to cause FileIO to check the DateTime stamp on all your index files when opening a new file, and automatically update any indexes that may have potentially gotten out of date from other programs.&lt;br /&gt;
&lt;br /&gt;
This option slows down the processing of the fnOpen function, particularly when running under client server over the internet. If you know that every program in your application suite uses FileIO, and that any programs that do not use FileIO still properly update all the indexes that your data file uses, then you can safely leave this setting turned off, and optimize your performance when opening several files using the fileIO system.&lt;br /&gt;
&lt;br /&gt;
==== CompressColumns ====&lt;br /&gt;
This instructs the datacrawler to use smaller widths for small columns. If CompressColumns is false, the data crawler will make the column the width of the field or the width of the caption, whichever is wider. If CompressColumns is true, it will use the width of the field, not the caption.&lt;br /&gt;
&lt;br /&gt;
==== MaxColWidth ====&lt;br /&gt;
This limits the column width to a set amount. This is applied after CompressColumns above.&lt;br /&gt;
&lt;br /&gt;
==== LogLibrary$ ====&lt;br /&gt;
If a library is specified here, then fileio looks for a BR library with the specified name, and attempts to call a library function in it called fnFileIOLog that. This function should expect six parameters, which are Logstring$, Login_Name$, Session$, Days of Date$, Time$, and CallingProgram$, if LogLayout is also nonblank.&lt;br /&gt;
&lt;br /&gt;
If you specify a LogLibrary and LogLayout is blank, then it calls your function giving it only 1 parameter.&lt;br /&gt;
&lt;br /&gt;
It will call your function &#039;&#039;&#039;&#039;&#039;instead of&#039;&#039;&#039;&#039;&#039; the normal log file. Use this to implement your own logging functions however you want.&lt;br /&gt;
&lt;br /&gt;
==== LogLayout$ ====&lt;br /&gt;
Use this setting to specify an alternate file layout for an alternate file to do the logging in. Base the file layout for this file on the logfile we supply for you in the filelay folder. It should have at least those fields, which our log routine will automatically populate, but you can add other fields if you want (though you will need to manage them yourself).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t specify LogLayout, the default filelay\logfile will be used. This will allow you to also use the fnViewLogFile function to access it.&lt;br /&gt;
&lt;br /&gt;
==== AnimateDataCrawler ====&lt;br /&gt;
The sister library [[ScreenIO]] has a feature that allows you to use an animation of a clock in your loading screens in your own programs. If you have [[ScreenIO]] then FileIO will automatically detect it and use it to display a loading animation while the data in the data crawler loads.&lt;br /&gt;
&lt;br /&gt;
Set AnimateDataCrawler to false (0) in your fileio.ini file to bypass the animations if you do have screenio but don&#039;t want to see the animations anyway.&lt;br /&gt;
&lt;br /&gt;
==== TemplatePath$ ====&lt;br /&gt;
This setting points to the folder that contains the code templates used by the Generate Code button on the main page of the Data Crawler.&lt;br /&gt;
&lt;br /&gt;
==== IgnoreLayouts$ ====&lt;br /&gt;
This is a comma delimited list of all layouts that you wish to suppress from appearing on the main page of the data crawler. You can still access these layouts with your code, but they won&#039;t be listed in the data crawler for you to access.&lt;br /&gt;
&lt;br /&gt;
Whatever you&#039;re using for a log layout, is automatically added to this list.&lt;br /&gt;
==== CloseFileSimple ====&lt;br /&gt;
The fnCloseFile function closes all the individual handles to a data file that was opened for output using multiple keys.&lt;br /&gt;
&lt;br /&gt;
For BR 4.18 and higher, we support a better algorithm for detecting which files are matches for a given opened file number.&lt;br /&gt;
&lt;br /&gt;
Set CloseFileSimple to true (1) to force it to check the old way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== FileIO Add-on Packages ==&lt;br /&gt;
&lt;br /&gt;
Many FileIO Add-on packages are currently in the works.&lt;br /&gt;
&lt;br /&gt;
=== ScreenIO Library ===&lt;br /&gt;
&lt;br /&gt;
The [[ScreenIO Library]] is a sister library to the FileIO library. The ScreenIO library requires the FileIO library in order to run.&lt;br /&gt;
&lt;br /&gt;
The ScreenIO library is a complete Rapid Application Design tool that enables you to implement custom screen functions anywhere in your exiting programs.&lt;br /&gt;
&lt;br /&gt;
If you call the ScreenIO Library as a function library, you call a function called fnFM and tell it which screen you wish to use. The ScreenIO library loads your user interface, loads your data files, and preforms all the file maintenance operations you have designed into your screens.&lt;br /&gt;
&lt;br /&gt;
You can find out the latest information about the ScreenIO library in the ScreenIO page.&lt;br /&gt;
&lt;br /&gt;
=== Audit BR ===&lt;br /&gt;
&lt;br /&gt;
The [[AuditBR|Audit BR]] developer tool makes a backup copy of your file layouts. Then you run some code you&#039;re trying to test, and finally, run Audit BR again, to get a report showing all the changes to any of your data files automatically.&lt;br /&gt;
&lt;br /&gt;
AuditBR is now included directly in FileIO. To use it, select the layout from the list of layouts in the Datacrawler and press the &amp;quot;Compare&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
== What’s New (also described above) ==&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
*Support for dates in any storage formats on disk (v2.48)&lt;br /&gt;
&lt;br /&gt;
=== 2015 ===&lt;br /&gt;
*Added Rec to display for fnShowData&lt;br /&gt;
*Added FormStatement$ for debugging of form statements inside fileio&lt;br /&gt;
*Added mat BadRead$ for debugging of 726 errors when making new layouts&lt;br /&gt;
*Fixed a bug causing crash when logging things from long program folders&lt;br /&gt;
*fnClientEnv$ - reads a Windows Environment variable on the client using the command shell&lt;br /&gt;
*fnAskCombo$ - added an optional parameter to set default selection.&lt;br /&gt;
&lt;br /&gt;
=== 2010 - 2014 ===&lt;br /&gt;
*FileIO now caches your file layouts in memory to make it much faster then before when opening the same data file in multiple programs.&lt;br /&gt;
*Generate Layout Wizard&lt;br /&gt;
*fnShowData&lt;br /&gt;
*Improved Logging&lt;br /&gt;
*fnViewLogFile&lt;br /&gt;
*Import/Export&lt;br /&gt;
*Import/Export by Function&lt;br /&gt;
*Many other speed increases&lt;br /&gt;
*if ScreenIO is present, Datacrawler uses the animation routines&lt;br /&gt;
*fnBuildProcFile &amp;amp; fnRunProcFile&lt;br /&gt;
*fnGenerateLayout &amp;amp; fnWriteLayout&lt;br /&gt;
*fnReadForm$&lt;br /&gt;
*fnReadFormAndSubs&lt;br /&gt;
*fnGetFileDateTime$&lt;br /&gt;
*fnReadLayoutPath$&lt;br /&gt;
*fnSortKeys&lt;br /&gt;
*fnSetLogChanges&lt;br /&gt;
*fnLogChanges&lt;br /&gt;
*fnLogArray&lt;br /&gt;
*fnReadScreenSize&lt;br /&gt;
*fnEmpty&lt;br /&gt;
*fnEmptyS&lt;br /&gt;
*Many other useful functions that were added to the documentation at earlier dates.&lt;br /&gt;
&lt;br /&gt;
=== Spring 2009 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;New Functions:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The FileIO Library has been updated in Spring 2009 to provide several new functions:&lt;br /&gt;
&lt;br /&gt;
*fnReadRecordWhere&lt;br /&gt;
*fnKey$&lt;br /&gt;
*fnBuildKey$&lt;br /&gt;
*fnReadUnopenedDescription$&lt;br /&gt;
*fnUpdateFile&lt;br /&gt;
*fnDisplayLength&lt;br /&gt;
*fnLength&lt;br /&gt;
*fnReadLayoutHeader&lt;br /&gt;
*fnReadEntireLayout&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Speed Increases:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Thanks to the BR [[Profiler]], we have increased the speed of FileIO fnOpen function by ten times when running over a LAN and by 100 times when running over the internet using Client Server.&lt;br /&gt;
&lt;br /&gt;
In order to get the new Speed Upgrades, you will need to make sure that you use the latest copy of the [[#fnOpen_Function|fnOpen]] function in each one of your programs that use FileIO.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Network FileIO:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
This version of FileIO has been optimized to work better in network situations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;FnSettings: &#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
There are more configuration options in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Automatic Update Speed Fix:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The automatic update proceedure has been made to run more then 100 times faster then before according to our benchmarking tests.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2008 ===&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler Grid:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By popular request we added a read/write version to the data crawler. This version works exactly the same as the datacrawler did before but it displays all the contents of your data files in a grid instead of a listview. &lt;br /&gt;
&lt;br /&gt;
When you run the fileIO library as a program, it launches a tool known as the [[#DataCrawler|DataCrawler]]. The DataCrawler shows a listview displaying all your layout files in it. If you select one, it builds another listview with all the data in your selected data file.&lt;br /&gt;
&lt;br /&gt;
If you are looking at the first list of all your file layouts, and you press F5, a grid will be build displaying all the data in your data files. You can change any of the records you like, and when you&#039;re done, your changes will be saved to the data file. Additionally, you can Add or Delete records using the buttons at the bottom. Any changes you make are not written to the disk until you click the &amp;quot;Save&amp;quot; button. If you press ESC (Cancel) the grid is closed and all changes you made sinse the last save are lost.&lt;br /&gt;
&lt;br /&gt;
This tool is for programmers only. Do not give your end users access to the DataCrawler.&lt;br /&gt;
&lt;br /&gt;
Use the DataCrawler at your own risk. Gabriel Bakker and Sage AX are not responsible for any harm that comes to your data files through the use of this or any other tools we offer.&lt;br /&gt;
&lt;br /&gt;
The DataCrawler is not designed to be used to maintain your data files. It can be used carefully to correct small things in your data files.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Paths:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Many BR Vendors keep different versions of the same data files in different locations. For example, sometimes a BR vendor will use a different Data folder to represent data for different Warehouses, or Customers. In cases like this it is necessary for your programs to specify the path to your data files. They may do so by specifying the optional &amp;quot;PATH&amp;quot; parameter to your data files. See the section [[#fnOpenFile]] for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Support for Nonstandard Layout Files Path:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Old versions of the fileIO library required you to place all your file layouts in a subfolder of your program directory called &amp;quot;filelay&amp;quot;. We have now changed the Fileio library to allow you to place your file layouts any place you want. The only requirement is that they are all together in one folder by themselves.&lt;br /&gt;
&lt;br /&gt;
If you use a nonstandard path in the fileIO library, it is necessary to make a change to the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code in your copy of fileio.br.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Prompt on Create:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The FileIO library automatically creates any data files that it can&#039;t find. This was done to make it easier to deploy your finished programs - if you had any empty data files you could omit them from the packages and they would be created when they were needed, on the fly.&lt;br /&gt;
&lt;br /&gt;
The current version of the FileIO library contains the same ability. However, it prompts you before creating any data files. This helps to avoid bugs that happen from incorrectly specifying the path to your data files.&lt;br /&gt;
&lt;br /&gt;
However, if you do intend for your data files to be autocreated, then you probably don&#039;t want your end users to modify them. Therefore, you can use the PromptOnCreate setting in the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] code to specify. If this value is set to true, then the fileIO library will prompt you whenever it attempts to Autocreate a data file. If it is set to false, then the fileIO library will create the data files without prompting you, like it always did before.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;fnSettings:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The newest version of the FileIO library supports some features that require you to specify Global Settings values. These are done by modifying the contents of the [[#fnSettings_.28Global_Settings_Code.29|fnSettings]] routine at the beginning of the fileIO library.&lt;br /&gt;
&lt;br /&gt;
=== Fall 2006 ===&lt;br /&gt;
Many improvements have been made in the FileIO library over the summer. This section is intended to acquaint you with the highlights of those changes. Most of these improvements were built from ideas generated during the discussion at the April conference.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intermixed String and Numeric Specs:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The File Library has been expanded to allow the reading of data files that contain mixed string and numeric specs. This is to aid those of you who are planning on implementing the FileIO Library on existing data files which may not be organized with strings first and numbers second.&lt;br /&gt;
&lt;br /&gt;
The central idea of the FileIO Library is based upon the reading of your data files into a String and Numeric array. This will enable you to refer to the fields in your file by using a named subscript, saying F$(FM_NAME) to refer, for example, to the farm file’s name field. The advantage to this is that when you change the file layout, all your existing programs will not have to be modified, because they will only be looking at F$(FM_NAME). If the name field changes from the third string field to the fourth, the value of FM_NAME will also change, and you won’t have to worry about updating your data files.&lt;br /&gt;
&lt;br /&gt;
However, in order to support the reading of a data file with intermixed string and numeric specs, the generated form statement will actually calculate the position of any fields that are not in order, so that the file read statement will still return all string fields first (into your string array) and the numeric fields second (into your numeric array). You do not need to worry about this; the library does it for you. All you have to do is read your file and use the data values.&lt;br /&gt;
&lt;br /&gt;
The only thing that is required is making sure that all your string subscript names end with a “$”. This will tell the library that they are strings. Thank you, George, for this suggestion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Versioning / Automatic Updates:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The file layouts have been expanded to contain a version number. This version number will be used to determine when a file needs to be updated. The version number is the third parameter in the file layout.&lt;br /&gt;
&lt;br /&gt;
Any time you wish to change your file layouts, simply increment this version number. Each time the library attempts to open a data file, the file’s version is checked and compared with the version in the file layout. If the file layout has been changed (if the version in the file layout is greater then the version in the file) then the file will be updated to the latest version. Depending on the file size this may take a couple of moments. A simple progress bar will be displayed on screen while this is happening. The progress bar will be displayed in its own window, so it should not affect anything your programs may have had on screen.&lt;br /&gt;
&lt;br /&gt;
The library uses the following procedure for updating a file. First, the file is copied to a backup file (prefixed by the letter ‘o’ for old). So color.dat would become ocolor.dat, and color.key would become ocolor.key. Then the new file is created and marked with the proper version number. (color.dat, color.key). The old file is opened in read-only mode, and the new file is opened for OutIn. The update routine actually reads through the old file layout, and one record at a time creates a new record, using the new file layout, with the same data, saving it to the new data file.&lt;br /&gt;
&lt;br /&gt;
When it is done upgrading, the progress bar window is closed, and the file is reopened in the fashion you described in your call to fnOpen, and flow returns to your program as though nothing unusual has happened.&lt;br /&gt;
&lt;br /&gt;
If an error occurs during processing, the routine will do what it can to roll your data files back to the previous version, but please make frequent backups of your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Error Checking – If there is a mistake in the file layout:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The routine attempts to discover the cause of the error in the case that one is encountered due to a missing file, a file sharing violation, or an invalid or corrupt file layout. If this should happen, the program is paused, and a text message is printed out explaining the most likely source for the error, including what part of the file layout, if any, may have caused the error.&lt;br /&gt;
&lt;br /&gt;
You may then examine the printed text, and the contents of the BR system variables ERR and LINE to determine the problem. If you type “GO”, the next line will be execute “system”, ending your program.&lt;br /&gt;
&lt;br /&gt;
If the file was in the middle of an upgrade when the error happened, it will be automatically rolled back to the previous version, so that when you fix the problem and try to run your program again, the file will again attempt to update from the beginning, and you won’t have to worry about corrupted data.&lt;br /&gt;
&lt;br /&gt;
However, please backup your data before upgrading your data files anyway, just to be safe.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Implementation of Keys / Creation of New Data Files:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
The library has been expanded to automatically create all new data files, and to automatically update any existing files. This means that in the file layout header, two new fields that were previously ignored are no longer ignored. The RECL value that you specify in your file layout will be used, along with the description/definition of your keys file. When you open a new file (or update an existing one), the library will calculate the proper kps and kln by evaluating the key description. This key description must match up with subscript values from the data elements in your file, and more then one may be specified, but they must be separated with slashes (/). If I wanted my Farm File to be keyed based on CODE and NAME, the proper key description would be:&lt;br /&gt;
&lt;br /&gt;
  farm.key, CODE/NAME&lt;br /&gt;
&lt;br /&gt;
The library will then look up the position and length on disk of the CODE and NAME fields and put them together to create the proper keys during an update or creation of a new file. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;DataCrawler:&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
Perhaps the most exciting new addition to the library is the addition of a programming tool I like to call a DataCrawler. If you run the FileIO Library directly as a program, instead of using it as a library, it will function as a DataCrawler. The DataCrawler requires a New Gui version of BR, as it requires a ListView to be able to properly and easily display the data in your files.&lt;br /&gt;
&lt;br /&gt;
If you run it in an older version of BR you will get a message, telling you to run it in a newer copy. If you run it in a New Gui version of BR with CONFIG GUI OFF, then GUI will be turned temporarily on for the use of the DataCrawler and then turned off again when you are finished. If you run it in a New Gui version of BR with GUI ON, it will just run normally.&lt;br /&gt;
&lt;br /&gt;
When you run the DataCrawler, first you will see a ListView displaying every file layout it can find in the filelay folder. If you select a file, the DataCrawler will open a large ListView with as many columns as there are fields in the file. The Column Headings will come from the element descriptions in your data files, and the column widths will come from the displayed width of the fields. There will be a row for every record in your data file, and you can resize the column widths, and scroll around the data file to view the raw data of your BR data files on disk.&lt;br /&gt;
&lt;br /&gt;
If you are dealing with a particularly enormous data file (50,000+ records) it can take a moment to populate the ListView with the data in your data file. You may hit ESC to stop loading if you like, and view only the already loaded records.&lt;br /&gt;
&lt;br /&gt;
If you would like to look up a particular record (based only upon the primary key for the data file), you may press F4. This will give you a window which asks you to input the key or partial key. When you press enter, the file will be reloaded, starting at the key you specified and continuing on to the end of the file, or until you press ESC. The records you are looking for should appear at the top of the ListView.&lt;br /&gt;
&lt;br /&gt;
To reset the ListView and look at the contents of the entire file again, simply press F4, and enter a blank (“”) key.&lt;br /&gt;
&lt;br /&gt;
Finally, as with any ListView, you may resort your data by clicking on any of the column headings. Then you can use the slider bar at the right to scroll down to the record you desire.&lt;br /&gt;
&lt;br /&gt;
This is a programming tool and is not designed to be used by an end user. This tool will open your file in read-only mode. It will not allow you to modify the data; I leave that as an exercise for the reader. However, it will update any datafiles you view to the latest version (if they need to be updated) when it opens them, just as any other program that uses the library will do.&lt;br /&gt;
&lt;br /&gt;
== Appendix (Examples)==&lt;br /&gt;
&lt;br /&gt;
=== Example.br ===&lt;br /&gt;
  00010    ! example.br - This program is an example of for the data reading simplification&lt;br /&gt;
  00020    ! Copyright April 2006 by Gabriel Bakker&lt;br /&gt;
  00030    ! Distributed open source as a Christmas gift to brag members&lt;br /&gt;
  00040    !&lt;br /&gt;
  00100    execute &amp;quot;config gui off&amp;quot;&lt;br /&gt;
  01020    DIM form$(1)*255&lt;br /&gt;
  01030    DIM color$(1)*255,color(1)&lt;br /&gt;
  01040    DIM colorcat$(1)*255,colorcat(1)&lt;br /&gt;
  02020    library &amp;quot;fileio&amp;quot;: fnopenfile, fnreaddescription$&lt;br /&gt;
  04000 !&lt;br /&gt;
  04010 Openfiles: ! Open your files here&lt;br /&gt;
  04020    let colorfile=fnopen(&amp;quot;color&amp;quot;,mat color$,mat color,mat form$)&lt;br /&gt;
  04030    let colorcatfile=fnopen(&amp;quot;colorcat&amp;quot;,mat colorcat$,mat colorcat,mat form$,1)&lt;br /&gt;
  06000    ! gosub WriteFiles ! If you want to test this line, make sure to drop flag from 4030&lt;br /&gt;
  07000 !&lt;br /&gt;
  07010 MainBit: ! This&#039;un here&#039;s tha Main Bit&lt;br /&gt;
  07030    RESTORE #ColorFile:&lt;br /&gt;
  07100    ReadNextcolor: ! Read next record&lt;br /&gt;
  07120       read #ColorFile, using form$(ColorFile) : mat Color$, mat Color eof EndReadColor&lt;br /&gt;
  07180       PRINT Color$(co_name)&amp;amp;&amp;quot; (&amp;quot;&amp;amp;Color$(co_html)&amp;amp;&amp;quot;) is a member of the &#039;&amp;quot;;&lt;br /&gt;
  07190       PRINT trim$(fnReadDescription$(ColorcatFile,cc_Name,Color$(co_category),mat Colorcat$,mat Colorcat,mat form$))&amp;amp;&amp;quot;&#039; category.&amp;quot;&lt;br /&gt;
  07290       goto ReadNextColor&lt;br /&gt;
  07300    EndReadcolor: ! Finished with Color File&lt;br /&gt;
  08000    STOP&lt;br /&gt;
  25000 !&lt;br /&gt;
  25010 WriteFiles: ! Uncalled routine to demonstrate writing files&lt;br /&gt;
  25105       let color$(co_code)=&amp;quot;GD&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Gold&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFD700&amp;quot;&lt;br /&gt;
  25110       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25115       let color$(co_code)=&amp;quot;LV&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Lavender&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;E6E6FA&amp;quot;&lt;br /&gt;
  25120       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25125       let color$(co_Code)=&amp;quot;OR&amp;quot; !:&lt;br /&gt;
              let color$(co_Name)=&amp;quot;Orange&amp;quot; !:&lt;br /&gt;
              let color$(co_Category)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let color$(co_html)=&amp;quot;FFA500&amp;quot;&lt;br /&gt;
  25130       write #colorfile, using form$(colorfile): mat color$, mat color&lt;br /&gt;
  25205       let colorcat$(cc_Code)=&amp;quot;YL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Yellows&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;FFFF00&amp;quot;&lt;br /&gt;
  25210       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25215       let colorcat$(cc_Code)=&amp;quot;BL&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_Name)=&amp;quot;Blues&amp;quot; !:&lt;br /&gt;
              let colorcat$(cc_html)=&amp;quot;0000FF&amp;quot;&lt;br /&gt;
  25220       write #colorcatfile, using form$(colorcatfile): mat colorcat$,mat colorcat&lt;br /&gt;
  25300    return&lt;br /&gt;
  40000 !&lt;br /&gt;
  40010 Open: ! ***** Function to call library openfile and proc subs&lt;br /&gt;
  40020    def fnOpen(FILENAME$, MAT F$, MAT F, MAT FORM$;INPUTONLY,KEYNUM,___,INDEX)&lt;br /&gt;
  40025       dim _FileIOSubs$(1)*50&lt;br /&gt;
  40030       let fnopen=fnopenfile(FILENAME$, MAT F$, MAT F, MAT FORM$,INPUTONLY,KEYNUM,MAT _FileIOSubs$)&lt;br /&gt;
  40040       for Index=1 to udim(mat _FileIOSubs$) : execute (_FileIOSubs$(Index)) : next Index&lt;br /&gt;
  40090    fnend&lt;br /&gt;
  50000 !&lt;br /&gt;
  60000 Ignore: Continue&lt;br /&gt;
  &lt;br /&gt;
  Color File Layout&lt;br /&gt;
  color.dat, CO_, 1&lt;br /&gt;
  color.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Color Code,                  C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  CATEGORY$,      General Category of Color,   C    6&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
&lt;br /&gt;
  ColorCat File Layout&lt;br /&gt;
  colorcat.dat, CC_, 0&lt;br /&gt;
  colorcat.key, CODE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  CODE$,          Category Code,               C    6&lt;br /&gt;
  NAME$,          English Name for Color,      V   30&lt;br /&gt;
  HTML$,          HTML Code for the Color,     C    6&lt;br /&gt;
  &lt;br /&gt;
Example Layout showing multiple keys (price)&lt;br /&gt;
  price.dat, PR_, 0&lt;br /&gt;
  price.key, FARM&lt;br /&gt;
  price.ky2, ITEM&lt;br /&gt;
  price.ky3, FARM/ITEM/GRADE&lt;br /&gt;
  recl=127&lt;br /&gt;
  ===================================================&lt;br /&gt;
  FARM$,          Farm Code (or blank),        C    4&lt;br /&gt;
  ITEM$,          Item Code,                   C    4&lt;br /&gt;
  GRADE$,         Quality,                     C    4&lt;br /&gt;
  X,              Empty,                       X   37&lt;br /&gt;
  PRICE,          Default Price,               BH 3.2&lt;br /&gt;
  COST,           Default Cost,                BH 3.2&lt;br /&gt;
  XOPRICE,        Default Christmas Price,     BH 3.2&lt;br /&gt;
  XOCOST,         Default Christmas Cost,      BH 3.2&lt;br /&gt;
  MOPRICE,        Default Mothers D Price,     BH 3.2&lt;br /&gt;
  MOCOST,         Default Mothers D Cost,      BH 3.2&lt;br /&gt;
  VOPRICE,        Default Valentine Price,     BH 3.2&lt;br /&gt;
  VOCOST,         Default Valentine Cost,      BH 3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Utilities_Third_Party]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=4271&amp;diff=11460</id>
		<title>4271</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=4271&amp;diff=11460"/>
		<updated>2024-09-03T02:41:40Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Error&lt;br /&gt;
|4271&lt;br /&gt;
|[[Operating System Error]] 71&lt;br /&gt;
|Incomplete record received/short record found.&lt;br /&gt;
|Incomplete record: retry the [[LINPUT]]. Short record: copy the file to a work area using the -D option to remove the partial record, then copy back the work file to the original.If the above description clearly does not apply to your situation, there is a possibility that you are receiving operating system error 71 Network request not accepted. See your Operating System manual for more information on dealing with this error.&lt;br /&gt;
&lt;br /&gt;
This error can also occur quite legitimately when reading files in [[External Files|External]] mode. Typically such files consist of a data stream of unknown length. When processing blocks of data in such files, the record length is set to chunk size and each READ accesses the next chunk of data. It is up to the program to decipher the actual layout of the data. In such cases, the last record read is often a fraction of the record length (chunk size) and an error 4271 is generated by the read.&lt;br /&gt;
&lt;br /&gt;
When an error 4271 occurs reading an External file, simply save the value of CNT and erform a REREAD into the same buffer as the read. The data length will be the saved CNT value and the remainder of the buffer will be set to binary zeroes. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
[[Category:Error Codes]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=SubProc&amp;diff=11459</id>
		<title>SubProc</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=SubProc&amp;diff=11459"/>
		<updated>2024-08-27T04:32:43Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;SubProc(SU)&#039;&#039;&#039; command runs one [[procedure]] from within another procedure. It performs the same function for a procedure file as a [[GoSub]] [[statement]] does for a [[program]]. This command can be issued by a program via the EXECUTE or CHAIN statement. For more details see the [[CHAIN]] statement.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
BR suspends the originating procedure when it finds the SubProc command in a procedure file. All active procedure files remain open and BR executes the entire sub-procedure before returning control to the originating procedure. Up to 9 levels of procedures may be nested.&lt;br /&gt;
&lt;br /&gt;
The following example starts execution of the SAMPLE procedure file:&lt;br /&gt;
&lt;br /&gt;
 SubProc B:Sample&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 SUBPROC &amp;lt;file name&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Image:SubProc.png]]&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
&lt;br /&gt;
SubProc requires the &#039;&#039;&#039;file name&#039;&#039;&#039; parameter, which specifies the file to be executed as a sub-procedure.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Procedure Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=SubProc&amp;diff=11458</id>
		<title>SubProc</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=SubProc&amp;diff=11458"/>
		<updated>2024-08-27T04:31:09Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;SubProc(SU)&#039;&#039;&#039; command runs one [[procedure]] from within another procedure. It performs the same function for a procedure file as a [[GoSub]] [[statement]] does for a [[program]]. This command can be issued by a program via the EXECUTE statement. For more details see the [[CHAIN]] statement.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
BR suspends the originating procedure when it finds the SubProc command in a procedure file. All active procedure files remain open and BR executes the entire sub-procedure before returning control to the originating procedure. Up to 9 levels of procedures may be nested.&lt;br /&gt;
&lt;br /&gt;
The following example starts execution of the SAMPLE procedure file:&lt;br /&gt;
&lt;br /&gt;
 SubProc B:Sample&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 SUBPROC &amp;lt;file name&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Image:SubProc.png]]&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
&lt;br /&gt;
SubProc requires the &#039;&#039;&#039;file name&#039;&#039;&#039; parameter, which specifies the file to be executed as a sub-procedure.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Procedure Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11457</id>
		<title>Chain</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11457"/>
		<updated>2024-08-27T04:24:00Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Chain&#039;&#039;&#039; (CH) [[statement]] ends the current program and starts execution of another program, procedure, or sub-procedure.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
CHAIN is most often used to chain from one program to another. To do this you just type CHAIN and indicate the name of the desired program.&lt;br /&gt;
&lt;br /&gt;
The statement in the following example causes the system to end the current program and load the program MENU.BR (or MENU.BRO) from a subdirectory called MAIN:&lt;br /&gt;
&lt;br /&gt;
 00350 CHAIN &amp;quot;MAIN\MENU&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The next example loads and runs the program GLEDIT.BR (or GLEDIT.BRO) from the subdirectory GLPROG (a subdirectory of the root directory). All files stay open at their current positions. The variable D$ retains its current value at the start of the new program; all other variables return to zeros or null strings.&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;C:\GLPROG\GLEDIT&amp;quot; ,FILES,D$&lt;br /&gt;
&lt;br /&gt;
To chain from a program to a procedure or sub-procedure (rather than another program), you must have either &amp;quot;PROC=&amp;quot; or &amp;quot;SUBPROC=&amp;quot; immediately before the name of the desired file, as in the following two examples:&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;PROC=GLPOST&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00080 NAME$=&amp;quot;EDITLIST.PRC&amp;quot;&lt;br /&gt;
 00090 X$=&amp;quot;SUBPROC=GLPROG\&amp;quot;&amp;amp;NAME$&lt;br /&gt;
 00100 CHAIN X$&lt;br /&gt;
&lt;br /&gt;
When a program chains to a procedure (PROC or SUBPROC), the procedure acts much like the operator stopped the program and started entering commands. A procedure is a set of commands. (However a procedure can skip forward or backward within itself.) While it is emulating a series of commands, the program that initiated the procedure is retained in memory even though the CHAIN statement terminated it. Therefore its variable contents are accessible to the procedure. &lt;br /&gt;
&lt;br /&gt;
The following example specifies that the string array A$ and the numeric variables B and C are to retain their values in the chained-to program:&lt;br /&gt;
&lt;br /&gt;
 60000 CHAIN PROG$,MAT A$,B,C&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 CHAIN {&amp;quot;&amp;lt;program name&amp;gt;&amp;quot;|”PROC=&amp;lt;name&amp;gt;”|”SUPROC=&amp;lt;name&amp;gt;”|”&amp;lt;path&amp;gt;\&amp;lt;name&amp;gt;”} [,FILES] [,MAT&amp;lt;array name&amp;gt;][,...] [,&amp;lt;variable name&amp;gt;][,...]&lt;br /&gt;
&lt;br /&gt;
[[file:Chain.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Load and run a program.&lt;br /&gt;
# Close all open files (except procedure files).&lt;br /&gt;
# Set all variables in the chained-to program to blanks or zeros.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The only required parameter of the CHAIN statement is the &amp;quot;file-ref&amp;quot;, which specifies the program, procedure, or sub-procedure to be executed. This name and subdirectory information may be specified as a quoted literal string or as a string variable. &lt;br /&gt;
&lt;br /&gt;
If the file-ref is preceded with the string &amp;quot;PROC=&amp;quot;, the CHAIN initiates a procedure. If the file-ref is preceded with the string &amp;quot;SUBPROC=&amp;quot;, the CHAIN initiates a sub-procedure. If neither of these keywords is present, the CHAIN statement attempts to load and run a program.&lt;br /&gt;
&lt;br /&gt;
The differtence between PROC and SUBPROC is that PROC will close the current (lowest level) PROC file (if one is running) before starting the specifified procedure, whereas SUBPROC will not affect a currently running PROC file. &lt;br /&gt;
&lt;br /&gt;
The parameters following the file-ref apply only to programs and not procedures.&lt;br /&gt;
&lt;br /&gt;
Following the file-ref information, CHAIN can take three optional parameters, but these parameters should be included only when chaining to a program.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;FILES&amp;quot; indicates that all files are to remain open and at their current positions. If you do not specify &amp;quot;FILES&amp;quot;, the CHAIN statement closes all files except procedure files.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;variables&amp;quot; parameters allow the specified arrays or variables to retain their current values in the chained-to program. If several are used they are seperated by commas.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Array variables and string variables passed between programs by a CHAIN statement, are not required to be dimensioned the same way in both programs. If dimensions do not match, the dimensions in the first program will override those in the DIM statements of the second program. However, it is recommended for improved readability that dimensions should match in both programs. Arrays may be re-dimensioned in the second program.&lt;br /&gt;
# Options selected in an OPTION statement are not required to be the same in both programs. However, it is strongly recommended that these options be the same. For example, if the first program uses BASE 1 and the second program uses BASE 0, confusing results could &amp;quot;run rampant&amp;quot; because the last element of a 10-element array would have a subscript of 10 in the first program and 9 in the second program.&lt;br /&gt;
# RUN command options, which are active in the initial program, will remain active in the chained program. These options include RUN PROC and output redirected to a file (see the RUN command for more information).&lt;br /&gt;
# In Business Rules, there is no need for a USE statement in the program being chained. USE is treated as a comment and is maintained only for compatibility with IBM Business BASIC.&lt;br /&gt;
# Although Business Rules checks most statements for proper syntax as they are entered, the file-ref parameter of the CHAIN statement is not checked until execution (this is also true in the OPEN statement). In this case, variables and quoted strings cannot be checked until execution.  # IBM Business BASIC restricted the use of CHAIN statements within IF statements; if the THEN clause was a CHAIN statement, the ELSE clause was not permitted. This restriction does not apply to Business Rules. The following statement is allowed: 90 IF X=0 THEN CHAIN &amp;quot;MENU&amp;quot; ELSE CHAIN &amp;quot;PR2&amp;quot;&lt;br /&gt;
# The rules for the default extension of a program name, which is specified in a CHAIN statement, are the same as the rules for the LOAD command. In short, the system first looks for an extension of .BR; if that is not present, the system then looks for .BRO. You can change these defaults with the CHAINDFLT specification in the BRConfig.sys file. You can also override the defaults from within the CHAIN statement by specifying your own extension, as in the following example:900 CHAIN &amp;quot;C:\MAIN\MENU.OLD&amp;quot;&lt;br /&gt;
# The ability to pass specified variables from a program to a procedure is not explicitly supported in Business Rules because the values of all variables are available at the end of a program. These variables retain their values until a SORT, INDEX, CLEAR, or LOAD command is encountered from the keyboard or from a procedure file. This means that any variable from the calling program could be tested by a SKIP command (or therwise used) in a procedure file. (Notice that this also means those values of all variables are available to an opertor after program termination for interrogation [displaying contents] and debugging.)&lt;br /&gt;
# Like most Business Rules statements, CHAIN may be used in immediate mode like a command (except with the EXECUTE statement, where commands may not terminate a program.) Within a procedure, the following two commands are equivalent: PROC EOM - CHAIN &amp;quot;PROC=EOM&amp;quot;. The next two commands would also be equivalent within a procedure: SUBPROC EOM - CHAIN &amp;quot;SUBPROC=EOM&amp;quot;. Although using CHAIN, as a command may not seem very useful because the simpler PROC and SUBPROC alternatives exist, CHAIN allows a procedure to pass variables from it&#039;s parent or itself to a program. For example, a procedure file might start a program to print designated messages for certain completion codes as follows: LOAD PROG1 - RUN - X=CODE - SKIP 1 IF X=0 - CHAIN &amp;quot;MESSAGE&amp;quot;, X. This passes the variable X (created by PROG1 or the procedure) to be passed to the program named MESSAGE. The same is true if a program CHAINS to a procedure and the procedure simply forwards variables from the chaining program to anotherr program.&lt;br /&gt;
# CHAIN &amp;quot;PROC=XYZ&amp;quot; is similar to EXECUTE &amp;quot;PROC XYZ&amp;quot;, except EXECUTE does not end the program. That being said, the procedure has the authority to end the program that called it via EXECUTE. &lt;br /&gt;
# If the FILES keyword is used to keep files open, file pointers are not moved. Thus, pointers, which were at the end of the file in the first program, will also be at the end of the file in the second program; you may use a RESTORE statement to reposition a file pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Ending Programs Statements]]&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11456</id>
		<title>Chain</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11456"/>
		<updated>2024-08-27T04:23:02Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Chain&#039;&#039;&#039; (CH) [[statement]] ends the current program and starts execution of another program, procedure, or sub-procedure.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
CHAIN is most often used to chain from one program to another. To do this you just type CHAIN and indicate the name of the desired program.&lt;br /&gt;
&lt;br /&gt;
The statement in the following example causes the system to end the current program and load the program MENU.BR (or MENU.BRO) from a subdirectory called MAIN:&lt;br /&gt;
&lt;br /&gt;
 00350 CHAIN &amp;quot;MAIN\MENU&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The next example loads and runs the program GLEDIT.BR (or GLEDIT.BRO) from the subdirectory GLPROG (a subdirectory of the root directory). All files stay open at their current positions. The variable D$ retains its current value at the start of the new program; all other variables return to zeros or null strings.&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;C:\GLPROG\GLEDIT&amp;quot; ,FILES,D$&lt;br /&gt;
&lt;br /&gt;
To chain from a program to a procedure or sub-procedure (rather than another program), you must have either &amp;quot;PROC=&amp;quot; or &amp;quot;SUBPROC=&amp;quot; immediately before the name of the desired file, as in the following two examples:&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;PROC=GLPOST&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00080 NAME$=&amp;quot;EDITLIST.PRC&amp;quot;&lt;br /&gt;
 00090 X$=&amp;quot;SUBPROC=GLPROG\&amp;quot;&amp;amp;NAME$&lt;br /&gt;
 00100 CHAIN X$&lt;br /&gt;
&lt;br /&gt;
When a program chains to a procedure (PROC or SUBPROC), the procedure acts much like the operator stopped the program and started entering commands. A procedure is a set of commands. (However a procedure can skip forward or backward within itself.) While it is emulating a series of commands, the program that initiated the procedure is retained in memory even though the CHAIN statement terminated it. Therefore its variable contents are accessible to the procedure. &lt;br /&gt;
&lt;br /&gt;
The following example specifies that the string array A$ and the numeric variables B and C are to retain their values in the chained-to program:&lt;br /&gt;
&lt;br /&gt;
 60000 CHAIN PROG$,MAT A$,B,C&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 CHAIN {&amp;quot;&amp;lt;program name&amp;gt;&amp;quot;|”PROC=&amp;lt;name&amp;gt;”|”SUPROC=&amp;lt;name&amp;gt;”|”&amp;lt;path&amp;gt;\&amp;lt;name&amp;gt;”} [,FILES] [,MAT&amp;lt;array name&amp;gt;][,...] [,&amp;lt;variable name&amp;gt;][,...]&lt;br /&gt;
&lt;br /&gt;
[[file:Chain.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Load and run a program.&lt;br /&gt;
# Close all open files (except procedure files).&lt;br /&gt;
# Set all variables in the chained-to program to blanks or zeros.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The only required parameter of the CHAIN statement is the &amp;quot;file-ref&amp;quot;, which specifies the program, procedure, or sub-procedure to be executed. This name and subdirectory information may be specified as a quoted literal string or as a string variable. &lt;br /&gt;
&lt;br /&gt;
If the file-ref is preceded with the string &amp;quot;PROC=&amp;quot;, the CHAIN initiates a procedure. If the file-ref is preceded with the string &amp;quot;SUBPROC=&amp;quot;, the CHAIN initiates a sub-procedure. If neither of these keywords is present, the CHAIN statement attempts to load and run a program.&lt;br /&gt;
&lt;br /&gt;
The differtence between PROC and SUBPROC is that PROC will close the current (lowest level) PROC file (if one is running) before starting the specifified procedure, whereas SUBPROC will not affect a currently running PROC file. &lt;br /&gt;
&lt;br /&gt;
The parameters following file-ref apply only to programs and not procedures.&lt;br /&gt;
&lt;br /&gt;
Following the file-ref information, CHAIN can take three optional parameters, but these parameters should be included only when chaining to a program.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;FILES&amp;quot; indicates that all files are to remain open and at their current positions. If you do not specify &amp;quot;FILES&amp;quot;, the CHAIN statement closes all files except procedure files.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;variables&amp;quot; parameters allow the specified arrays or variables to retain their current values in the chained-to program. If several are used they are seperated by commas.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Array variables and string variables passed between programs by a CHAIN statement, are not required to be dimensioned the same way in both programs. If dimensions do not match, the dimensions in the first program will override those in the DIM statements of the second program. However, it is recommended for improved readability that dimensions should match in both programs. Arrays may be re-dimensioned in the second program.&lt;br /&gt;
# Options selected in an OPTION statement are not required to be the same in both programs. However, it is strongly recommended that these options be the same. For example, if the first program uses BASE 1 and the second program uses BASE 0, confusing results could &amp;quot;run rampant&amp;quot; because the last element of a 10-element array would have a subscript of 10 in the first program and 9 in the second program.&lt;br /&gt;
# RUN command options, which are active in the initial program, will remain active in the chained program. These options include RUN PROC and output redirected to a file (see the RUN command for more information).&lt;br /&gt;
# In Business Rules, there is no need for a USE statement in the program being chained. USE is treated as a comment and is maintained only for compatibility with IBM Business BASIC.&lt;br /&gt;
# Although Business Rules checks most statements for proper syntax as they are entered, the file-ref parameter of the CHAIN statement is not checked until execution (this is also true in the OPEN statement). In this case, variables and quoted strings cannot be checked until execution.  # IBM Business BASIC restricted the use of CHAIN statements within IF statements; if the THEN clause was a CHAIN statement, the ELSE clause was not permitted. This restriction does not apply to Business Rules. The following statement is allowed: 90 IF X=0 THEN CHAIN &amp;quot;MENU&amp;quot; ELSE CHAIN &amp;quot;PR2&amp;quot;&lt;br /&gt;
# The rules for the default extension of a program name, which is specified in a CHAIN statement, are the same as the rules for the LOAD command. In short, the system first looks for an extension of .BR; if that is not present, the system then looks for .BRO. You can change these defaults with the CHAINDFLT specification in the BRConfig.sys file. You can also override the defaults from within the CHAIN statement by specifying your own extension, as in the following example:900 CHAIN &amp;quot;C:\MAIN\MENU.OLD&amp;quot;&lt;br /&gt;
# The ability to pass specified variables from a program to a procedure is not explicitly supported in Business Rules because the values of all variables are available at the end of a program. These variables retain their values until a SORT, INDEX, CLEAR, or LOAD command is encountered from the keyboard or from a procedure file. This means that any variable from the calling program could be tested by a SKIP command (or therwise used) in a procedure file. (Notice that this also means those values of all variables are available to an opertor after program termination for interrogation [displaying contents] and debugging.)&lt;br /&gt;
# Like most Business Rules statements, CHAIN may be used in immediate mode like a command (except with the EXECUTE statement, where commands may not terminate a program.) Within a procedure, the following two commands are equivalent: PROC EOM - CHAIN &amp;quot;PROC=EOM&amp;quot;. The next two commands would also be equivalent within a procedure: SUBPROC EOM - CHAIN &amp;quot;SUBPROC=EOM&amp;quot;. Although using CHAIN, as a command may not seem very useful because the simpler PROC and SUBPROC alternatives exist, CHAIN allows a procedure to pass variables from it&#039;s parent or itself to a program. For example, a procedure file might start a program to print designated messages for certain completion codes as follows: LOAD PROG1 - RUN - X=CODE - SKIP 1 IF X=0 - CHAIN &amp;quot;MESSAGE&amp;quot;, X. This passes the variable X (created by PROG1 or the procedure) to be passed to the program named MESSAGE. The same is true if a program CHAINS to a procedure and the procedure simply forwards variables from the chaining program to anotherr program.&lt;br /&gt;
# CHAIN &amp;quot;PROC=XYZ&amp;quot; is similar to EXECUTE &amp;quot;PROC XYZ&amp;quot;, except EXECUTE does not end the program. That being said, the procedure has the authority to end the program that called it via EXECUTE. &lt;br /&gt;
# If the FILES keyword is used to keep files open, file pointers are not moved. Thus, pointers, which were at the end of the file in the first program, will also be at the end of the file in the second program; you may use a RESTORE statement to reposition a file pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Ending Programs Statements]]&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11455</id>
		<title>Chain</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11455"/>
		<updated>2024-08-27T04:18:18Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Technical Considerations */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Chain&#039;&#039;&#039; (CH) [[statement]] ends the current program and starts execution of another program, procedure, or sub-procedure.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
CHAIN is most often used to chain from one program to another. To do this you just type CHAIN and indicate the name of the desired program.&lt;br /&gt;
&lt;br /&gt;
The statement in the following example causes the system to end the current program and load the program MENU.BR (or MENU.BRO) from a subdirectory called MAIN:&lt;br /&gt;
&lt;br /&gt;
 00350 CHAIN &amp;quot;MAIN\MENU&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The next example loads and runs the program GLEDIT.BR (or GLEDIT.BRO) from the subdirectory GLPROG (a subdirectory of the root directory). All files stay open at their current positions. The variable D$ retains its current value at the start of the new program; all other variables return to zeros or null strings.&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;C:\GLPROG\GLEDIT&amp;quot; ,FILES,D$&lt;br /&gt;
&lt;br /&gt;
To chain from a program to a procedure or sub-procedure (rather than another program), you must have either &amp;quot;PROC=&amp;quot; or &amp;quot;SUBPROC=&amp;quot; immediately before the name of the desired file, as in the following two examples:&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;PROC=GLPOST&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00080 NAME$=&amp;quot;EDITLIST.PRC&amp;quot;&lt;br /&gt;
 00090 X$=&amp;quot;SUBPROC=GLPROG\&amp;quot;&amp;amp;NAME$&lt;br /&gt;
 00100 CHAIN X$&lt;br /&gt;
&lt;br /&gt;
When a program chains to a procedure (PROC or SUBPROC), the procedure acts much like the operator stopped the program and started entering commands. A procedure is a set of commands. (However a procedure can skip forward or backward within itself.) While it is emulating a series of commands, the program that initiated the procedure is retained in memory even though the CHAIN statement terminated it. Therefore its variable contents are accessible to the procedure. &lt;br /&gt;
&lt;br /&gt;
The following example specifies that the string array A$ and the numeric variables B and C are to retain their values in the chained-to program:&lt;br /&gt;
&lt;br /&gt;
 60000 CHAIN PROG$,MAT A$,B,C&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 CHAIN {&amp;quot;&amp;lt;program name&amp;gt;&amp;quot;|”PROC=&amp;lt;name&amp;gt;”|”SUPROC=&amp;lt;name&amp;gt;”|”&amp;lt;path&amp;gt;\&amp;lt;name&amp;gt;”} [,FILES] [,MAT&amp;lt;array name&amp;gt;][,...] [,&amp;lt;variable name&amp;gt;][,...]&lt;br /&gt;
&lt;br /&gt;
[[file:Chain.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Load and run a program.&lt;br /&gt;
# Close all open files (except procedure files).&lt;br /&gt;
# Set all variables in the chained-to program to blanks or zeros.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The only required parameter of the CHAIN statement is the &amp;quot;file-ref&amp;quot;, which specifies the program, procedure, or sub-procedure to be executed. This name and subdirectory information may be specified as a quoted literal string or as a string variable. &lt;br /&gt;
&lt;br /&gt;
If the file-ref is preceded with the string &amp;quot;PROC=&amp;quot;, the CHAIN initiates a procedure. If the file-ref is preceded with the string &amp;quot;SUBPROC=&amp;quot;, the CHAIN initiates a sub-procedure. If neither of these keywords is present, the CHAIN statement attempts to load and run a program.&lt;br /&gt;
&lt;br /&gt;
The differtence between PROC and SUBPROC is that PROC will close the current (lowest level) PROC file (if one is running) before starting the specifified procedure, whereas SUBPROC will not affect a currently running PROC file. &lt;br /&gt;
&lt;br /&gt;
Following the file-ref information, CHAIN can take three optional parameters, but these parameters should be included only when chaining to a program.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;FILES&amp;quot; indicates that all files are to remain open and at their current positions. If you do not specify &amp;quot;FILES&amp;quot;, the CHAIN statement closes all files except procedure files.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;variables&amp;quot; parameters allow the specified arrays or variables to retain their current values in the chained-to program. If several are used they are seperated by commas.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Array variables and string variables passed between programs by a CHAIN statement, are not required to be dimensioned the same way in both programs. If dimensions do not match, the dimensions in the first program will override those in the DIM statements of the second program. However, it is recommended for improved readability that dimensions should match in both programs. Arrays may be re-dimensioned in the second program.&lt;br /&gt;
# Options selected in an OPTION statement are not required to be the same in both programs. However, it is strongly recommended that these options be the same. For example, if the first program uses BASE 1 and the second program uses BASE 0, confusing results could &amp;quot;run rampant&amp;quot; because the last element of a 10-element array would have a subscript of 10 in the first program and 9 in the second program.&lt;br /&gt;
# RUN command options, which are active in the initial program, will remain active in the chained program. These options include RUN PROC and output redirected to a file (see the RUN command for more information).&lt;br /&gt;
# In Business Rules, there is no need for a USE statement in the program being chained. USE is treated as a comment and is maintained only for compatibility with IBM Business BASIC.&lt;br /&gt;
# Although Business Rules checks most statements for proper syntax as they are entered, the file-ref parameter of the CHAIN statement is not checked until execution (this is also true in the OPEN statement). In this case, variables and quoted strings cannot be checked until execution.  # IBM Business BASIC restricted the use of CHAIN statements within IF statements; if the THEN clause was a CHAIN statement, the ELSE clause was not permitted. This restriction does not apply to Business Rules. The following statement is allowed: 90 IF X=0 THEN CHAIN &amp;quot;MENU&amp;quot; ELSE CHAIN &amp;quot;PR2&amp;quot;&lt;br /&gt;
# The rules for the default extension of a program name, which is specified in a CHAIN statement, are the same as the rules for the LOAD command. In short, the system first looks for an extension of .BR; if that is not present, the system then looks for .BRO. You can change these defaults with the CHAINDFLT specification in the BRConfig.sys file. You can also override the defaults from within the CHAIN statement by specifying your own extension, as in the following example:900 CHAIN &amp;quot;C:\MAIN\MENU.OLD&amp;quot;&lt;br /&gt;
# The ability to pass specified variables from a program to a procedure is not explicitly supported in Business Rules because the values of all variables are available at the end of a program. These variables retain their values until a SORT, INDEX, CLEAR, or LOAD command is encountered from the keyboard or from a procedure file. This means that any variable from the calling program could be tested by a SKIP command (or therwise used) in a procedure file. (Notice that this also means those values of all variables are available to an opertor after program termination for interrogation [displaying contents] and debugging.)&lt;br /&gt;
# Like most Business Rules statements, CHAIN may be used in immediate mode like a command (except with the EXECUTE statement, where commands may not terminate a program.) Within a procedure, the following two commands are equivalent: PROC EOM - CHAIN &amp;quot;PROC=EOM&amp;quot;. The next two commands would also be equivalent within a procedure: SUBPROC EOM - CHAIN &amp;quot;SUBPROC=EOM&amp;quot;. Although using CHAIN, as a command may not seem very useful because the simpler PROC and SUBPROC alternatives exist, CHAIN allows a procedure to pass variables from it&#039;s parent or itself to a program. For example, a procedure file might start a program to print designated messages for certain completion codes as follows: LOAD PROG1 - RUN - X=CODE - SKIP 1 IF X=0 - CHAIN &amp;quot;MESSAGE&amp;quot;, X. This passes the variable X (created by PROG1 or the procedure) to be passed to the program named MESSAGE. The same is true if a program CHAINS to a procedure and the procedure simply forwards variables from the chaining program to anotherr program.&lt;br /&gt;
# CHAIN &amp;quot;PROC=XYZ&amp;quot; is similar to EXECUTE &amp;quot;PROC XYZ&amp;quot;, except EXECUTE does not end the program. That being said, the procedure has the authority to end the program that called it via EXECUTE. &lt;br /&gt;
# If the FILES keyword is used to keep files open, file pointers are not moved. Thus, pointers, which were at the end of the file in the first program, will also be at the end of the file in the second program; you may use a RESTORE statement to reposition a file pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Ending Programs Statements]]&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11454</id>
		<title>Chain</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11454"/>
		<updated>2024-08-27T03:52:04Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Comments and Examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Chain&#039;&#039;&#039; (CH) [[statement]] ends the current program and starts execution of another program, procedure, or sub-procedure.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
CHAIN is most often used to chain from one program to another. To do this you just type CHAIN and indicate the name of the desired program.&lt;br /&gt;
&lt;br /&gt;
The statement in the following example causes the system to end the current program and load the program MENU.BR (or MENU.BRO) from a subdirectory called MAIN:&lt;br /&gt;
&lt;br /&gt;
 00350 CHAIN &amp;quot;MAIN\MENU&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The next example loads and runs the program GLEDIT.BR (or GLEDIT.BRO) from the subdirectory GLPROG (a subdirectory of the root directory). All files stay open at their current positions. The variable D$ retains its current value at the start of the new program; all other variables return to zeros or null strings.&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;C:\GLPROG\GLEDIT&amp;quot; ,FILES,D$&lt;br /&gt;
&lt;br /&gt;
To chain from a program to a procedure or sub-procedure (rather than another program), you must have either &amp;quot;PROC=&amp;quot; or &amp;quot;SUBPROC=&amp;quot; immediately before the name of the desired file, as in the following two examples:&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;PROC=GLPOST&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00080 NAME$=&amp;quot;EDITLIST.PRC&amp;quot;&lt;br /&gt;
 00090 X$=&amp;quot;SUBPROC=GLPROG\&amp;quot;&amp;amp;NAME$&lt;br /&gt;
 00100 CHAIN X$&lt;br /&gt;
&lt;br /&gt;
When a program chains to a procedure (PROC or SUBPROC), the procedure acts much like the operator stopped the program and started entering commands. A procedure is a set of commands. (However a procedure can skip forward or backward within itself.) While it is emulating a series of commands, the program that initiated the procedure is retained in memory even though the CHAIN statement terminated it. Therefore its variable contents are accessible to the procedure. &lt;br /&gt;
&lt;br /&gt;
The following example specifies that the string array A$ and the numeric variables B and C are to retain their values in the chained-to program:&lt;br /&gt;
&lt;br /&gt;
 60000 CHAIN PROG$,MAT A$,B,C&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 CHAIN {&amp;quot;&amp;lt;program name&amp;gt;&amp;quot;|”PROC=&amp;lt;name&amp;gt;”|”SUPROC=&amp;lt;name&amp;gt;”|”&amp;lt;path&amp;gt;\&amp;lt;name&amp;gt;”} [,FILES] [,MAT&amp;lt;array name&amp;gt;][,...] [,&amp;lt;variable name&amp;gt;][,...]&lt;br /&gt;
&lt;br /&gt;
[[file:Chain.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Load and run a program.&lt;br /&gt;
# Close all open files (except procedure files).&lt;br /&gt;
# Set all variables in the chained-to program to blanks or zeros.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The only required parameter of the CHAIN statement is the &amp;quot;file-ref&amp;quot;, which specifies the program, procedure, or sub-procedure to be executed. This name and subdirectory information may be specified as a quoted literal string or as a string variable. &lt;br /&gt;
&lt;br /&gt;
If the file-ref is preceded with the string &amp;quot;PROC=&amp;quot;, the CHAIN initiates a procedure. If the file-ref is preceded with the string &amp;quot;SUBPROC=&amp;quot;, the CHAIN initiates a sub-procedure. If neither of these keywords is present, the CHAIN statement attempts to load and run a program.&lt;br /&gt;
&lt;br /&gt;
The differtence between PROC and SUBPROC is that PROC will close the current (lowest level) PROC file (if one is running) before starting the specifified procedure, whereas SUBPROC will not affect a currently running PROC file. &lt;br /&gt;
&lt;br /&gt;
Following the file-ref information, CHAIN can take three optional parameters, but these parameters should be included only when chaining to a program.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;FILES&amp;quot; indicates that all files are to remain open and at their current positions. If you do not specify &amp;quot;FILES&amp;quot;, the CHAIN statement closes all files except procedure files.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;variables&amp;quot; parameters allow the specified arrays or variables to retain their current values in the chained-to program. If several are used they are seperated by commas.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Array variables and string variables passed between programs by a CHAIN statement, are not required to be dimensioned the same way in both programs. If dimensions do not match, the dimensions in the first program will override those in the DIM statements of the second program. However, it is recommended for improved readability that dimensions should match in both programs. Arrays may be re-dimensioned in the second program.&lt;br /&gt;
# Options selected in an OPTION statement are not required to be the same in both programs. However, it is strongly recommended that these options be the same. For example, if the first program uses BASE 1 and the second program uses BASE 0, confusing results could &amp;quot;run rampant&amp;quot; because the last element of a 10-element array would have a subscript of 10 in the first program and 9 in the second program.&lt;br /&gt;
# RUN command options, which are active in the initial program, will remain active in the chained program. These options include RUN PROC and output redirected to a file (see the RUN command for more information).&lt;br /&gt;
# In Business Rules, there is no need for a USE statement in the program being chained. USE is treated as a comment and is maintained only for compatibility with IBM Business BASIC.&lt;br /&gt;
# Although Business Rules checks most statements for proper syntax as they are entered, the file-ref parameter of the CHAIN statement is not checked until execution (this is also true in the OPEN statement). In this case, variables and quoted strings cannot be checked until execution. Execution errors may arise from text entered as literally inside quotes. The same errors result when a string variable is used as the file-ref if that string does not validly specify an existing file.&lt;br /&gt;
# IBM Business BASIC restricted the use of CHAIN statements within IF statements; if the THEN clause was a CHAIN statement, the ELSE clause was not permitted. This restriction does not apply to Business Rules. The following statement is allowed: 90 IF X=0 THEN CHAIN &amp;quot;MENU&amp;quot; ELSE CHAIN &amp;quot;PR2&amp;quot;&lt;br /&gt;
# The rules for the default extension of a program name, which is specified in a CHAIN statement, are the same as the rules for the LOAD command. In short, the system first looks for an extension of .BR; if that is not present, the system then looks for .BRO. You can change these defaults with the CHAINDFLT specification in the BRConfig.sys file. You can also override the defaults from within the CHAIN statement by specifying your own extension, as in the following example:900 CHAIN &amp;quot;C:\MAIN\MENU.OLD&amp;quot;&lt;br /&gt;
# The ability to pass specified variables from a program to a procedure is not explicitly supported in Business Rules because all values of all variables are available at the end of a program. These variables retain their values until a SORT, INDEX, CLEAR, or LOAD command is encountered from the keyboard or from a procedure file. This means that any variable from the previous program could be tested by a SKIP command in a procedure file. (Notice that this also means those values of all variables are still available after program termination for printing and debugging.)&lt;br /&gt;
# Like most Business Rules statements, CHAIN may be used in immediate mode like a command (except with the EXECUTE statement, where commands may not terminate a program.) Within a procedure, the following two commands are equivalent: PROC EOM - CHAIN &amp;quot;PROC=EOM&amp;quot;The next two commands would also be equivalent within a procedure: SUBPROC EOM - CHAIN &amp;quot;SUBPROC=EOM&amp;quot; Although using CHAIN, as a command may not seem very useful because the simpler PROC and SUBPROC alternatives exist, it does allow for variables to be passed from a procedure to a program. For example, a procedure file might start a program to print designated messages for certain completion codes as follows: LOAD PROG1 - RUN - X=CODE - SKIP 1 IF X=0 - CHAIN &amp;quot;MESSAGE&amp;quot;, X&lt;br /&gt;
# If the FILES keyword is used to keep files open, file pointers are not moved. Thus, pointers, which were at the end of the file in the first program, will also be at the end of the file in the second program; you may use a RESTORE statement to reposition a file pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Ending Programs Statements]]&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11453</id>
		<title>Chain</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11453"/>
		<updated>2024-08-27T03:29:14Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Chain&#039;&#039;&#039; (CH) [[statement]] ends the current program and starts execution of another program, procedure, or sub-procedure.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
CHAIN is most often used to chain from one program to another. To do this you just type CHAIN and indicate the name of the desired program.&lt;br /&gt;
&lt;br /&gt;
The statement in the following example causes the system to end the current program and load the program MENU.BR (or MENU.BRO) from a subdirectory called MAIN:&lt;br /&gt;
&lt;br /&gt;
 00350 CHAIN &amp;quot;MAIN\MENU&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The next example loads and runs the program GLEDIT.BR (or GLEDIT.BRO) from the subdirectory GLPROG (a subdirectory of the root directory). All files stay open at their current positions. The variable D$ retains its current value at the start of the new program; all other variables return to zeros or null strings.&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;C:\GLPROG\GLEDIT&amp;quot; ,FILES,D$&lt;br /&gt;
&lt;br /&gt;
To chain from a program to a procedure or sub-procedure (rather than another program), you must have either &amp;quot;PROC=&amp;quot; or &amp;quot;SUBPROC=&amp;quot; immediately before the name of the desired file, as in the following two examples:&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;PROC=GLPOST&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00080 NAME$=&amp;quot;EDITLIST&amp;quot;&lt;br /&gt;
 00090 X$=&amp;quot;SUBPROC=GLPROG\&amp;quot;&amp;amp;NAME$&lt;br /&gt;
 00100 CHAIN X$&lt;br /&gt;
&lt;br /&gt;
The following example specifies that the string array A$ and the numeric variables B and C are to retain their values in the chained-to program:&lt;br /&gt;
&lt;br /&gt;
 60000 CHAIN PROG$,MAT A$,B,C&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 CHAIN {&amp;quot;&amp;lt;program name&amp;gt;&amp;quot;|”PROC=&amp;lt;name&amp;gt;”|”SUPROC=&amp;lt;name&amp;gt;”|”&amp;lt;path&amp;gt;\&amp;lt;name&amp;gt;”} [,FILES] [,MAT&amp;lt;array name&amp;gt;][,...] [,&amp;lt;variable name&amp;gt;][,...]&lt;br /&gt;
&lt;br /&gt;
[[file:Chain.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Load and run a program.&lt;br /&gt;
# Close all open files (except procedure files).&lt;br /&gt;
# Set all variables in the chained-to program to blanks or zeros.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The only required parameter of the CHAIN statement is the &amp;quot;file-ref&amp;quot;, which specifies the program, procedure, or sub-procedure to be executed. This name and subdirectory information may be specified as a quoted literal string or as a string variable. &lt;br /&gt;
&lt;br /&gt;
If the file-ref is preceded with the string &amp;quot;PROC=&amp;quot;, the CHAIN initiates a procedure. If the file-ref is preceded with the string &amp;quot;SUBPROC=&amp;quot;, the CHAIN initiates a sub-procedure. If neither of these keywords is present, the CHAIN statement attempts to load and run a program.&lt;br /&gt;
&lt;br /&gt;
The differtence between PROC and SUBPROC is that PROC will close the current (lowest level) PROC file (if one is running) before starting the specifified procedure, whereas SUBPROC will not affect a currently running PROC file. &lt;br /&gt;
&lt;br /&gt;
Following the file-ref information, CHAIN can take three optional parameters, but these parameters should be included only when chaining to a program.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;FILES&amp;quot; indicates that all files are to remain open and at their current positions. If you do not specify &amp;quot;FILES&amp;quot;, the CHAIN statement closes all files except procedure files.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;variables&amp;quot; parameters allow the specified arrays or variables to retain their current values in the chained-to program. If several are used they are seperated by commas.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Array variables and string variables passed between programs by a CHAIN statement, are not required to be dimensioned the same way in both programs. If dimensions do not match, the dimensions in the first program will override those in the DIM statements of the second program. However, it is recommended for improved readability that dimensions should match in both programs. Arrays may be re-dimensioned in the second program.&lt;br /&gt;
# Options selected in an OPTION statement are not required to be the same in both programs. However, it is strongly recommended that these options be the same. For example, if the first program uses BASE 1 and the second program uses BASE 0, confusing results could &amp;quot;run rampant&amp;quot; because the last element of a 10-element array would have a subscript of 10 in the first program and 9 in the second program.&lt;br /&gt;
# RUN command options, which are active in the initial program, will remain active in the chained program. These options include RUN PROC and output redirected to a file (see the RUN command for more information).&lt;br /&gt;
# In Business Rules, there is no need for a USE statement in the program being chained. USE is treated as a comment and is maintained only for compatibility with IBM Business BASIC.&lt;br /&gt;
# Although Business Rules checks most statements for proper syntax as they are entered, the file-ref parameter of the CHAIN statement is not checked until execution (this is also true in the OPEN statement). In this case, variables and quoted strings cannot be checked until execution. Execution errors may arise from text entered as literally inside quotes. The same errors result when a string variable is used as the file-ref if that string does not validly specify an existing file.&lt;br /&gt;
# IBM Business BASIC restricted the use of CHAIN statements within IF statements; if the THEN clause was a CHAIN statement, the ELSE clause was not permitted. This restriction does not apply to Business Rules. The following statement is allowed: 90 IF X=0 THEN CHAIN &amp;quot;MENU&amp;quot; ELSE CHAIN &amp;quot;PR2&amp;quot;&lt;br /&gt;
# The rules for the default extension of a program name, which is specified in a CHAIN statement, are the same as the rules for the LOAD command. In short, the system first looks for an extension of .BR; if that is not present, the system then looks for .BRO. You can change these defaults with the CHAINDFLT specification in the BRConfig.sys file. You can also override the defaults from within the CHAIN statement by specifying your own extension, as in the following example:900 CHAIN &amp;quot;C:\MAIN\MENU.OLD&amp;quot;&lt;br /&gt;
# The ability to pass specified variables from a program to a procedure is not explicitly supported in Business Rules because all values of all variables are available at the end of a program. These variables retain their values until a SORT, INDEX, CLEAR, or LOAD command is encountered from the keyboard or from a procedure file. This means that any variable from the previous program could be tested by a SKIP command in a procedure file. (Notice that this also means those values of all variables are still available after program termination for printing and debugging.)&lt;br /&gt;
# Like most Business Rules statements, CHAIN may be used in immediate mode like a command (except with the EXECUTE statement, where commands may not terminate a program.) Within a procedure, the following two commands are equivalent: PROC EOM - CHAIN &amp;quot;PROC=EOM&amp;quot;The next two commands would also be equivalent within a procedure: SUBPROC EOM - CHAIN &amp;quot;SUBPROC=EOM&amp;quot; Although using CHAIN, as a command may not seem very useful because the simpler PROC and SUBPROC alternatives exist, it does allow for variables to be passed from a procedure to a program. For example, a procedure file might start a program to print designated messages for certain completion codes as follows: LOAD PROG1 - RUN - X=CODE - SKIP 1 IF X=0 - CHAIN &amp;quot;MESSAGE&amp;quot;, X&lt;br /&gt;
# If the FILES keyword is used to keep files open, file pointers are not moved. Thus, pointers, which were at the end of the file in the first program, will also be at the end of the file in the second program; you may use a RESTORE statement to reposition a file pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Ending Programs Statements]]&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11452</id>
		<title>Chain</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11452"/>
		<updated>2024-08-27T03:10:49Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Syntax */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Chain&#039;&#039;&#039; (CH) [[statement]] ends the current program and starts execution of another program, procedure, or sub-procedure.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
CHAIN is most often used to chain from one program to another. To do this you just type CHAIN and indicate the name of the desired program.&lt;br /&gt;
&lt;br /&gt;
The statement in the following example causes the system to end the current program and load the program MENU.BR (or MENU.BRO) from a subdirectory called MAIN:&lt;br /&gt;
&lt;br /&gt;
 00350 CHAIN &amp;quot;MAIN\MENU&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The next example loads and runs the program GLEDIT.BR (or GLEDIT.BRO) from the subdirectory GLPROG (a subdirectory of the root directory). All files stay open at their current positions. The variable D$ retains its current value at the start of the new program; all other variables return to zeros or null strings.&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;C:\GLPROG\GLEDIT&amp;quot; ,FILES,D$&lt;br /&gt;
&lt;br /&gt;
To chain from a program to a procedure or sub-procedure (rather than another program), you must have either &amp;quot;PROC=&amp;quot; or &amp;quot;SUBPROC=&amp;quot; immediately before the name of the desired file, as in the following two examples:&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;PROC=GLPOST&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00080 NAME$=&amp;quot;EDITLIST&amp;quot;&lt;br /&gt;
 00090 X$=&amp;quot;SUBPROC=GLPROG\&amp;quot;&amp;amp;NAME$&lt;br /&gt;
 00100 CHAIN X$&lt;br /&gt;
&lt;br /&gt;
The following example specifies that the string array A$ and the numeric variables B and C are to retain their values in the chained-to program:&lt;br /&gt;
&lt;br /&gt;
 60000 CHAIN PROG$,MAT A$,B,C&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 CHAIN {&amp;quot;&amp;lt;program name&amp;gt;&amp;quot;|”PROC=&amp;lt;name&amp;gt;”|”SUPROC=&amp;lt;name&amp;gt;”|”&amp;lt;path&amp;gt;\&amp;lt;name&amp;gt;”} [,FILES] [,MAT&amp;lt;array name&amp;gt;][,...] [,&amp;lt;variable name&amp;gt;][,...]&lt;br /&gt;
&lt;br /&gt;
[[file:Chain.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Load and run a program.&lt;br /&gt;
# Close all open files (except procedure files).&lt;br /&gt;
# Set all variables in the chained-to program to blanks or zeros.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The only required parameter of the CHAIN statement is the &amp;quot;file-ref&amp;quot;, which specifies the program, procedure, or sub-procedure to be executed. This name and subdirectory information may be specified as a quoted literal string or as a string variable. If the file-ref is preceded with the string &amp;quot;PROC=&amp;quot;, the CHAIN initiates a procedure. If the file-ref is preceded with the string &amp;quot;SUBPROC=&amp;quot;, the CHAIN initiates a sub-procedure. If neither of these is present, the CHAIN statement attempts to load and run a program.&lt;br /&gt;
&lt;br /&gt;
Following the file-ref information, CHAIN can take three optional parameters, but these parameters should be included only when chaining to a program.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;FILES&amp;quot; indicates that all files are to remain open and at their current positions. If you do not specify &amp;quot;FILES&amp;quot;, the CHAIN statement closes all files except procedure files.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;variables&amp;quot; parameters allow the specified arrays or variables to retain their current values in the chained program. If several are used they are seperated by commas.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Array variables and string variables passed between programs by a CHAIN statement, are not required to be dimensioned the same way in both programs. If dimensions do not match, the dimensions in the first program will override those in the DIM statements of the second program. However, it is recommended for improved readability that dimensions should match in both programs. Arrays may be re-dimensioned in the second program.&lt;br /&gt;
# Options selected in an OPTION statement are not required to be the same in both programs. However, it is strongly recommended that these options be the same. For example, if the first program uses BASE 1 and the second program uses BASE 0, confusing results could &amp;quot;run rampant&amp;quot; because the last element of a 10-element array would have a subscript of 10 in the first program and 9 in the second program.&lt;br /&gt;
# RUN command options, which are active in the initial program, will remain active in the chained program. These options include RUN PROC and output redirected to a file (see the RUN command for more information).&lt;br /&gt;
# In Business Rules, there is no need for a USE statement in the program being chained. USE is treated as a comment and is maintained only for compatibility with IBM Business BASIC.&lt;br /&gt;
# Although Business Rules checks most statements for proper syntax as they are entered, the file-ref parameter of the CHAIN statement is not checked until execution (this is also true in the OPEN statement). In this case, variables and quoted strings cannot be checked until execution. Execution errors may arise from text entered as literally inside quotes. The same errors result when a string variable is used as the file-ref if that string does not validly specify an existing file.&lt;br /&gt;
# IBM Business BASIC restricted the use of CHAIN statements within IF statements; if the THEN clause was a CHAIN statement, the ELSE clause was not permitted. This restriction does not apply to Business Rules. The following statement is allowed: 90 IF X=0 THEN CHAIN &amp;quot;MENU&amp;quot; ELSE CHAIN &amp;quot;PR2&amp;quot;&lt;br /&gt;
# The rules for the default extension of a program name, which is specified in a CHAIN statement, are the same as the rules for the LOAD command. In short, the system first looks for an extension of .BR; if that is not present, the system then looks for .BRO. You can change these defaults with the CHAINDFLT specification in the BRConfig.sys file. You can also override the defaults from within the CHAIN statement by specifying your own extension, as in the following example:900 CHAIN &amp;quot;C:\MAIN\MENU.OLD&amp;quot;&lt;br /&gt;
# The ability to pass specified variables from a program to a procedure is not explicitly supported in Business Rules because all values of all variables are available at the end of a program. These variables retain their values until a SORT, INDEX, CLEAR, or LOAD command is encountered from the keyboard or from a procedure file. This means that any variable from the previous program could be tested by a SKIP command in a procedure file. (Notice that this also means those values of all variables are still available after program termination for printing and debugging.)&lt;br /&gt;
# Like most Business Rules statements, CHAIN may be used in immediate mode like a command (except with the EXECUTE statement, where commands may not terminate a program.) Within a procedure, the following two commands are equivalent: PROC EOM - CHAIN &amp;quot;PROC=EOM&amp;quot;The next two commands would also be equivalent within a procedure: SUBPROC EOM - CHAIN &amp;quot;SUBPROC=EOM&amp;quot; Although using CHAIN, as a command may not seem very useful because the simpler PROC and SUBPROC alternatives exist, it does allow for variables to be passed from a procedure to a program. For example, a procedure file might start a program to print designated messages for certain completion codes as follows: LOAD PROG1 - RUN - X=CODE - SKIP 1 IF X=0 - CHAIN &amp;quot;MESSAGE&amp;quot;, X&lt;br /&gt;
# If the FILES keyword is used to keep files open, file pointers are not moved. Thus, pointers, which were at the end of the file in the first program, will also be at the end of the file in the second program; you may use a RESTORE statement to reposition a file pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Ending Programs Statements]]&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=SpoolCmd&amp;diff=11451</id>
		<title>SpoolCmd</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=SpoolCmd&amp;diff=11451"/>
		<updated>2024-08-08T03:52:25Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Syntax */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SPOOLCMD is mainly useful for producing print files for Linux and MAC systems. In Windows systems the DIRECT://printer-name is preferred. Also see the [[PRINTING]] section.&lt;br /&gt;
&lt;br /&gt;
Basically, SpoolCmd provides a description of how to print data sent to the printer.&lt;br /&gt;
&lt;br /&gt;
Business Rules supports independent spoolers on all systems. The [[BRConfig.sys]] SPOOLCMD specification will make a [[shell call]] to issue the necessary spool command whenever printing is done. When this is invoked the spooler is responsible for removing the spool file created by Business Rules.&lt;br /&gt;
&lt;br /&gt;
====Syntax====&lt;br /&gt;
 SPOOLCMD spool-program [SPOOLFILE] [NAME=&amp;lt;name&amp;gt;] [QUEUE] [PRINTER] [COPIES] [WSID] [PROGRAM]&lt;br /&gt;
&lt;br /&gt;
The spool command and its parameters can be specified in a [[CONFIG]] command or in the [[BRConfig.sys]] file as follows:&lt;br /&gt;
&lt;br /&gt;
[[file:Spoolcmd.png|750px]]&lt;br /&gt;
&lt;br /&gt;
Business Rules can also make substitutions as follows in parameters specified with SPOOLCMD (they&#039;re all optional):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[SPOOLFILE]&#039;&#039;&#039; substitutes the name of the spool file, as determined by Business Rules.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[NAME=]&#039;&#039;&#039; substitutes the name of the printer file as specified in the OPEN display statement&#039;s NAME= string. For example, PRN:/10.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[QUEUE]&#039;&#039;&#039; substitutes the queue name as specified in the OPEN display statement. In the following example, &amp;quot;queue&amp;quot; would be substituted for [QUEUE]: PRN:queue/10.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[PRINTER]&#039;&#039;&#039; substitutes the &amp;quot;printerclass&amp;quot; string as specified in the [[OPEN display]] statement. For example, PRN:/printerclass.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[COPIES]&#039;&#039;&#039; substitutes the value of the COPIES= parameter as specified in the OPEN display statement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[WSID]&#039;&#039;&#039; substitutes the user&#039;s workstation ID.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[PROGRAM]&#039;&#039;&#039; substitutes the name of the program that is loaded at the time that the printer is closed.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;[QUEUE]&#039;&#039;&#039; and &#039;&#039;&#039;[PRINTER]&#039;&#039;&#039; parameters can be used almost interchangeably. They are simply a versatile way to pass specifications from the Open (or SUBSTITUTE) statement to the spooler.&lt;br /&gt;
&lt;br /&gt;
NOTE: The spoolfile created by Business Rules will not automatically be deleted.&lt;br /&gt;
&lt;br /&gt;
SPOOLCMD supports the -w flag which avoids the use of a DOS shell command when calling Windows applications directly. The Windows Client SPOOLCMD syntax is:&lt;br /&gt;
&lt;br /&gt;
   SPOOLCMD [@] [-w] command  parm-1 parm-2 ...&lt;br /&gt;
&lt;br /&gt;
SPOOLCMD honors BR drive substitution on the command itself, for example:&lt;br /&gt;
  DRIVE  J:,M:\myapp,j,\&lt;br /&gt;
  SPOOLCMD J:\mylib\spooler [SPOOLFILE] [COPIES]&lt;br /&gt;
&lt;br /&gt;
This finds &#039;spooler&#039; in M:\myapp\mylib.&lt;br /&gt;
&lt;br /&gt;
See also:&lt;br /&gt;
*[[SpoolFile]]&lt;br /&gt;
*[[SpoolPath]]&lt;br /&gt;
&lt;br /&gt;
====Config Spoolcmd====&lt;br /&gt;
As of 4.3, the [[CONFIG]] command supports the SPOOLCMD spec, which enables any system spooler to work with BR. See the &amp;quot;SPOOLCMD&amp;quot; specification in the BRConfig.sys Specification section for additional information. On DOS and NetWork versions, OPENs to PRN: will return error [[6298]] when the following two conditions are true:&lt;br /&gt;
&lt;br /&gt;
:1) SPOOLCMD is not specified&lt;br /&gt;
:2) The printer is not ready.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Config]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11448</id>
		<title>Chain</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Chain&amp;diff=11448"/>
		<updated>2024-03-03T06:23:29Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Syntax */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Chain&#039;&#039;&#039; (CH) [[statement]] ends the current program and starts execution of another program, procedure, or sub-procedure.&lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
CHAIN is most often used to chain from one program to another. To do this you just type CHAIN and indicate the name of the desired program.&lt;br /&gt;
&lt;br /&gt;
The statement in the following example causes the system to end the current program and load the program MENU.BR (or MENU.BRO) from a subdirectory called MAIN:&lt;br /&gt;
&lt;br /&gt;
 00350 CHAIN &amp;quot;MAIN\MENU&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The next example loads and runs the program GLEDIT.BR (or GLEDIT.BRO) from the subdirectory GLPROG (a subdirectory of the root directory). All files stay open at their current positions. The variable D$ retains its current value at the start of the new program; all other variables return to zeros or null strings.&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;C:\GLPROG\GLEDIT&amp;quot; ,FILES,D$&lt;br /&gt;
&lt;br /&gt;
To chain from a program to a procedure or sub-procedure (rather than another program), you must have either &amp;quot;PROC=&amp;quot; or &amp;quot;SUBPROC=&amp;quot; immediately before the name of the desired file, as in the following two examples:&lt;br /&gt;
&lt;br /&gt;
 00900 CHAIN &amp;quot;PROC=GLPOST&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 00080 NAME$=&amp;quot;EDITLIST&amp;quot;&lt;br /&gt;
 00090 X$=&amp;quot;SUBPROC=GLPROG\&amp;quot;&amp;amp;NAME$&lt;br /&gt;
 00100 CHAIN X$&lt;br /&gt;
&lt;br /&gt;
The following example specifies that the string array A$ and the numeric variables B and C are to retain their values in the chained-to program:&lt;br /&gt;
&lt;br /&gt;
 60000 CHAIN PROG$,MAT A$,B,C&lt;br /&gt;
&lt;br /&gt;
===Syntax===&lt;br /&gt;
 CHAIN {&amp;quot;&amp;lt;program name&amp;gt;&amp;quot;|”PROC=&amp;lt;name&amp;gt;”|”SUPROC=&amp;lt;name&amp;gt;”|”&amp;lt;path&amp;gt;\&amp;lt;name&amp;gt;”} [,FILES] [,MAT&amp;lt;array name&amp;gt;][,...] [,&amp;lt;variables&amp;gt;][,...]&lt;br /&gt;
&lt;br /&gt;
[[file:Chain.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Defaults===&lt;br /&gt;
# Load and run a program.&lt;br /&gt;
# Close all open files (except procedure files).&lt;br /&gt;
# Set all variables in the chained-to program to blanks or zeros.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
The only required parameter of the CHAIN statement is the &amp;quot;file-ref&amp;quot;, which specifies the program, procedure, or sub-procedure to be executed. This name and subdirectory information may be specified as a quoted literal string or as a string variable. If the file-ref is preceded with the string &amp;quot;PROC=&amp;quot;, the CHAIN initiates a procedure. If the file-ref is preceded with the string &amp;quot;SUBPROC=&amp;quot;, the CHAIN initiates a sub-procedure. If neither of these is present, the CHAIN statement attempts to load and run a program.&lt;br /&gt;
&lt;br /&gt;
Following the file-ref information, CHAIN can take three optional parameters, but these parameters should be included only when chaining to a program.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;FILES&amp;quot; indicates that all files are to remain open and at their current positions. If you do not specify &amp;quot;FILES&amp;quot;, the CHAIN statement closes all files except procedure files.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;MAT array-name&amp;quot; and &amp;quot;variables&amp;quot; parameters allow the specified arrays or variables to retain their current values in the chained program. If several are used they are seperated by commas.&lt;br /&gt;
&lt;br /&gt;
===Technical Considerations===&lt;br /&gt;
# Array variables and string variables passed between programs by a CHAIN statement, are not required to be dimensioned the same way in both programs. If dimensions do not match, the dimensions in the first program will override those in the DIM statements of the second program. However, it is recommended for improved readability that dimensions should match in both programs. Arrays may be re-dimensioned in the second program.&lt;br /&gt;
# Options selected in an OPTION statement are not required to be the same in both programs. However, it is strongly recommended that these options be the same. For example, if the first program uses BASE 1 and the second program uses BASE 0, confusing results could &amp;quot;run rampant&amp;quot; because the last element of a 10-element array would have a subscript of 10 in the first program and 9 in the second program.&lt;br /&gt;
# RUN command options, which are active in the initial program, will remain active in the chained program. These options include RUN PROC and output redirected to a file (see the RUN command for more information).&lt;br /&gt;
# In Business Rules, there is no need for a USE statement in the program being chained. USE is treated as a comment and is maintained only for compatibility with IBM Business BASIC.&lt;br /&gt;
# Although Business Rules checks most statements for proper syntax as they are entered, the file-ref parameter of the CHAIN statement is not checked until execution (this is also true in the OPEN statement). In this case, variables and quoted strings cannot be checked until execution. Execution errors may arise from text entered as literally inside quotes. The same errors result when a string variable is used as the file-ref if that string does not validly specify an existing file.&lt;br /&gt;
# IBM Business BASIC restricted the use of CHAIN statements within IF statements; if the THEN clause was a CHAIN statement, the ELSE clause was not permitted. This restriction does not apply to Business Rules. The following statement is allowed: 90 IF X=0 THEN CHAIN &amp;quot;MENU&amp;quot; ELSE CHAIN &amp;quot;PR2&amp;quot;&lt;br /&gt;
# The rules for the default extension of a program name, which is specified in a CHAIN statement, are the same as the rules for the LOAD command. In short, the system first looks for an extension of .BR; if that is not present, the system then looks for .BRO. You can change these defaults with the CHAINDFLT specification in the BRConfig.sys file. You can also override the defaults from within the CHAIN statement by specifying your own extension, as in the following example:900 CHAIN &amp;quot;C:\MAIN\MENU.OLD&amp;quot;&lt;br /&gt;
# The ability to pass specified variables from a program to a procedure is not explicitly supported in Business Rules because all values of all variables are available at the end of a program. These variables retain their values until a SORT, INDEX, CLEAR, or LOAD command is encountered from the keyboard or from a procedure file. This means that any variable from the previous program could be tested by a SKIP command in a procedure file. (Notice that this also means those values of all variables are still available after program termination for printing and debugging.)&lt;br /&gt;
# Like most Business Rules statements, CHAIN may be used in immediate mode like a command (except with the EXECUTE statement, where commands may not terminate a program.) Within a procedure, the following two commands are equivalent: PROC EOM - CHAIN &amp;quot;PROC=EOM&amp;quot;The next two commands would also be equivalent within a procedure: SUBPROC EOM - CHAIN &amp;quot;SUBPROC=EOM&amp;quot; Although using CHAIN, as a command may not seem very useful because the simpler PROC and SUBPROC alternatives exist, it does allow for variables to be passed from a procedure to a program. For example, a procedure file might start a program to print designated messages for certain completion codes as follows: LOAD PROG1 - RUN - X=CODE - SKIP 1 IF X=0 - CHAIN &amp;quot;MESSAGE&amp;quot;, X&lt;br /&gt;
# If the FILES keyword is used to keep files open, file pointers are not moved. Thus, pointers, which were at the end of the file in the first program, will also be at the end of the file in the second program; you may use a RESTORE statement to reposition a file pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Ending Programs Statements]]&lt;br /&gt;
[[Category:Statements]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Run&amp;diff=11447</id>
		<title>Run</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Run&amp;diff=11447"/>
		<updated>2024-02-28T19:34:45Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Run (RU)&#039;&#039;&#039; [[command]] begins execution of a program. When RUN is used to redirect printer output to a file or screen (RUN&amp;gt; filename) the redirection is now stopped only when the system returns to READY mode (when no programs or procedures remain active). This allows redirection to work with a procedure file that contains multiple RUN commands. Previously, the redirection remained active only until the next RUN command was issued.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
RUN sets all variables to zero (or zero length) and begins executing at the lowest line number of the program. Open files stay open. Execution halts and an error message is displayed on the status line when an error occurs during program execution.&lt;br /&gt;
&lt;br /&gt;
A running program stops when it reaches a STOP, END, or CHAIN statement within the program; all open files close (except when the optional FILES parameter is used in a CHAIN statement). A running program pauses when it reaches a PAUSE statement or when you press the Ctrl-A key combination. To resume execution, enter a GO command (see the options for the GO command in the GO section of this chapter).&lt;br /&gt;
&lt;br /&gt;
The following command begins execution of the program in memory at line 350:&lt;br /&gt;
&lt;br /&gt;
 RUN 350&lt;br /&gt;
&lt;br /&gt;
The next command executes the program in memory and sends all printer output to the file REPORT. The use of the double redirection arrow (&amp;gt;&amp;gt;) causes the output to be appended to the end of REPORT (rather than to overwrite it):&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;&amp;gt;REPORT&lt;br /&gt;
&lt;br /&gt;
The command below begins execution of the program in memory and directs printer output to the screen:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;CON:&lt;br /&gt;
&lt;br /&gt;
The following command loads and begins execution of the program SCORE. All TRACE output is sent to the printer.&lt;br /&gt;
&lt;br /&gt;
 RUN B:SCORE TRACE PRINT&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 RUN &amp;lt;file name&amp;gt; [ [[TRACE]] [PRINT]][...] [{[[STEP]]|[[PROC]]|&amp;lt;[[line number]]&amp;gt;|&amp;gt;[&amp;gt;]&amp;lt;file name&amp;gt;}[...]&lt;br /&gt;
&lt;br /&gt;
[[Image:Run.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
&lt;br /&gt;
# RUN the program currently in memory.&lt;br /&gt;
# Send TRACE output to the screen.&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;File-name&amp;quot;&#039;&#039;&#039;|| is the name and path of the file you wish to run. BR to see that the named file exists, then performs a CLEAR operation and loads and begins executing the specified file. File names which are the same as any of RUN&#039;s parameters (TRACE, PRINT, STEP or PROC) cannot be specified with the RUN command. Also, file names that are also numbers cannot be specified with RUN (the number will be interpreted as a &amp;quot;line-num&amp;quot; parameter -see below) unless the extension is also specified (i.e., RUN 123.BR). It is important to note that the system will always clear current memory and load the specified file, even if that file is currently in memory -thus the potential exists for losing unstored program lines.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;TRACE&amp;quot; &#039;&#039;&#039;||displays the line number of each line as it is being executed. Unlike STEP, it does not cause the program to pause before executing each statement. This parameter is very useful for debugging purposes. To turn TRACE off, you must interrupt BR (Ctrl-A) and use GO RUN to continue execution.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;PRINT&amp;quot; &#039;&#039;&#039;||sends TRACE output to an attached printer.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;STEP&amp;quot; &#039;&#039;&#039;||pauses the program before each statement is executed. The line number field in the status line tells you the next line number to be executed. That line is executed and the next line number appears when &amp;lt;CR&amp;gt; is pressed. STEP is useful for debugging of programs, as it allows you to closely follow the program&#039;s flow.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;PROC&amp;quot; &#039;&#039;&#039;||accept input from the procedure file that contains this command, for all INPUT, LINPUT, and RINPUT statements. Normally the input for these statements would come from the keyboard. The RUN PROC option has no effect on INPUT FIELDS or RINPUT FIELDS statements. (see Technical Considerations below)&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If you wish to start running the program from a point other than the beginning, you can use &amp;quot;line-num&amp;quot; to specify the first line of execution.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;File_ref saves to a file all program output that normally goes to the printer. Use of one redirection arrow (&amp;gt;) causes output to overwrite the contents of the specified file. Two redirection arrows (&amp;gt;&amp;gt;) cause the output to be appended to the end of the specified file.&lt;br /&gt;
&lt;br /&gt;
The &amp;gt;file-ref can also redirect printer output to other communications devices. To send output to the screen, for example, use:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;CON:&lt;br /&gt;
&lt;br /&gt;
To send it to whatever device is connected to the first communications port, use:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;AUX:&lt;br /&gt;
&lt;br /&gt;
These strings must be translated with the SUBSTITUTE specification for BR or Linux versions. BR sends all program results to the specified file-ref location until you enter another RUN command.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
# BR closes a procedure before executing the last line. Thus when RUN file-ref is included as the last line, there is a clean trade-off in the number of open files on the system. But RUN PROC cannot be used as the last line because the procedure will be closed before the program starts to look for data in the procedure (an easy solution is to add a comment line ( ! - exclamation point ) after the RUN PROC command).&lt;br /&gt;
# RUN &amp;gt;file-ref remains in effect until another RUN command is issued. Output redirected to a file is not accessible until the file is closed by another RUN command.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Program Management Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Run&amp;diff=11446</id>
		<title>Run</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Run&amp;diff=11446"/>
		<updated>2024-02-28T19:33:23Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Technical Considerations */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Run (RU)&#039;&#039;&#039; [[command]] begins execution of a program. When RUN is used to redirect printer output to a file or screen (RUN&amp;gt; filename) the redirection is now stopped only when the system returns to READY mode (when no programs or procedures remain active). This allows redirection to work with a procedure file that contains multiple RUN commands. Previously, the redirection remained active only until the next RUN command was issued.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
RUN sets all variables to zero (or zero length) and begins executing at the lowest line number of the program. Open files stay open. Execution halts and an error message is displayed on the status line when an error occurs during program execution.&lt;br /&gt;
&lt;br /&gt;
A running program stops when it reaches a STOP, END, or CHAIN statement within the program; all open files close (except when the optional FILES parameter is used in a CHAIN statement). A running program pauses when it reaches a PAUSE statement or when you press the Ctrl-A key combination. To resume execution, enter a GO command (see the options for the GO command in the GO section of this chapter).&lt;br /&gt;
&lt;br /&gt;
The following command begins execution of the program in memory at line 350:&lt;br /&gt;
&lt;br /&gt;
 RUN 350&lt;br /&gt;
&lt;br /&gt;
The next command executes the program in memory and sends all printer output to the file REPORT. The use of the double redirection arrow (&amp;gt;&amp;gt;) causes the output to be appended to the end of REPORT (rather than to overwrite it):&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;&amp;gt;REPORT&lt;br /&gt;
&lt;br /&gt;
The command below begins execution of the program in memory and directs printer output to the screen:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;CON:&lt;br /&gt;
&lt;br /&gt;
The following command loads and begins execution of the program SCORE. All TRACE output is sent to the printer.&lt;br /&gt;
&lt;br /&gt;
 RUN B:SCORE TRACE PRINT&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 RUN &amp;lt;file name&amp;gt; [ [[TRACE]] [PRINT]][...] [{[[STEP]]|[[PROC]]|&amp;lt;[[line number]]&amp;gt;|&amp;gt;[&amp;gt;]&amp;lt;file name&amp;gt;}[...]&lt;br /&gt;
&lt;br /&gt;
[[Image:Run.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
&lt;br /&gt;
# RUN the program currently in memory.&lt;br /&gt;
# Send TRACE output to the screen.&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;File-name&amp;quot;&#039;&#039;&#039;|| is the name and path of the file you wish to run. BR to see that the named file exists, then performs a CLEAR operation and loads and begins executing the specified file. File names which are the same as any of RUN&#039;s parameters (TRACE, PRINT, STEP or PROC) cannot be specified with the RUN command. Also, file names that are also numbers cannot be specified with RUN (the number will be interpreted as a &amp;quot;line-num&amp;quot; parameter -see below) unless the extension is also specified (i.e., RUN 123.BR). It is important to note that the system will always clear current memory and load the specified file, even if that file is currently in memory -thus the potential exists for losing unstored program lines.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;TRACE&amp;quot; &#039;&#039;&#039;||displays the line number of each line as it is being executed. Unlike STEP, it does not cause the program to pause before executing each statement. This parameter is very useful for debugging purposes. To turn TRACE off, you must interrupt BR (Ctrl-A) and use GO RUN to continue execution.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;PRINT&amp;quot; &#039;&#039;&#039;||sends TRACE output to an attached printer.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;STEP&amp;quot; &#039;&#039;&#039;||pauses the program before each statement is executed. The line number field in the status line tells you the next line number to be executed. That line is executed and the next line number appears when &amp;lt;CR&amp;gt; is pressed. STEP is useful for debugging of programs, as it allows you to closely follow the program&#039;s flow.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;PROC&amp;quot; &#039;&#039;&#039;||accept input from the procedure file that contains this command, for all INPUT, LINPUT, and RINPUT statements. Normally the input for these statements would come from the keyboard. The RUN PROC option has no effect on INPUT FIELDS or RINPUT FIELDS statements.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If you wish to start running the program from a point other than the beginning, you can use &amp;quot;line-num&amp;quot; to specify the first line of execution.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;File_ref saves to a file all program output that normally goes to the printer. Use of one redirection arrow (&amp;gt;) causes output to overwrite the contents of the specified file. Two redirection arrows (&amp;gt;&amp;gt;) cause the output to be appended to the end of the specified file.&lt;br /&gt;
&lt;br /&gt;
The &amp;gt;file-ref can also redirect printer output to other communications devices. To send output to the screen, for example, use:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;CON:&lt;br /&gt;
&lt;br /&gt;
To send it to whatever device is connected to the first communications port, use:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;AUX:&lt;br /&gt;
&lt;br /&gt;
These strings must be translated with the SUBSTITUTE specification for BR or Linux versions. BR sends all program results to the specified file-ref location until you enter another RUN command.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
# BR closes a procedure before executing the last line. Thus when RUN file-ref is included as the last line, there is a clean trade-off in the number of open files on the system. But RUN PROC cannot be used as the last line because the procedure will be closed before the program starts to look for data in the procedure (an easy solution is to add a comment line ( ! - exclamation point ) after the RUN PROC command).&lt;br /&gt;
# RUN &amp;gt;file-ref remains in effect until another RUN command is issued. Output redirected to a file is not accessible until the file is closed by another RUN command.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Program Management Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Run&amp;diff=11445</id>
		<title>Run</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Run&amp;diff=11445"/>
		<updated>2024-02-28T19:30:26Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Run (RU)&#039;&#039;&#039; [[command]] begins execution of a program. When RUN is used to redirect printer output to a file or screen (RUN&amp;gt; filename) the redirection is now stopped only when the system returns to READY mode (when no programs or procedures remain active). This allows redirection to work with a procedure file that contains multiple RUN commands. Previously, the redirection remained active only until the next RUN command was issued.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
RUN sets all variables to zero (or zero length) and begins executing at the lowest line number of the program. Open files stay open. Execution halts and an error message is displayed on the status line when an error occurs during program execution.&lt;br /&gt;
&lt;br /&gt;
A running program stops when it reaches a STOP, END, or CHAIN statement within the program; all open files close (except when the optional FILES parameter is used in a CHAIN statement). A running program pauses when it reaches a PAUSE statement or when you press the Ctrl-A key combination. To resume execution, enter a GO command (see the options for the GO command in the GO section of this chapter).&lt;br /&gt;
&lt;br /&gt;
The following command begins execution of the program in memory at line 350:&lt;br /&gt;
&lt;br /&gt;
 RUN 350&lt;br /&gt;
&lt;br /&gt;
The next command executes the program in memory and sends all printer output to the file REPORT. The use of the double redirection arrow (&amp;gt;&amp;gt;) causes the output to be appended to the end of REPORT (rather than to overwrite it):&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;&amp;gt;REPORT&lt;br /&gt;
&lt;br /&gt;
The command below begins execution of the program in memory and directs printer output to the screen:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;CON:&lt;br /&gt;
&lt;br /&gt;
The following command loads and begins execution of the program SCORE. All TRACE output is sent to the printer.&lt;br /&gt;
&lt;br /&gt;
 RUN B:SCORE TRACE PRINT&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 RUN &amp;lt;file name&amp;gt; [ [[TRACE]] [PRINT]][...] [{[[STEP]]|[[PROC]]|&amp;lt;[[line number]]&amp;gt;|&amp;gt;[&amp;gt;]&amp;lt;file name&amp;gt;}[...]&lt;br /&gt;
&lt;br /&gt;
[[Image:Run.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
&lt;br /&gt;
# RUN the program currently in memory.&lt;br /&gt;
# Send TRACE output to the screen.&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;File-name&amp;quot;&#039;&#039;&#039;|| is the name and path of the file you wish to run. BR to see that the named file exists, then performs a CLEAR operation and loads and begins executing the specified file. File names which are the same as any of RUN&#039;s parameters (TRACE, PRINT, STEP or PROC) cannot be specified with the RUN command. Also, file names that are also numbers cannot be specified with RUN (the number will be interpreted as a &amp;quot;line-num&amp;quot; parameter -see below) unless the extension is also specified (i.e., RUN 123.BR). It is important to note that the system will always clear current memory and load the specified file, even if that file is currently in memory -thus the potential exists for losing unstored program lines.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;TRACE&amp;quot; &#039;&#039;&#039;||displays the line number of each line as it is being executed. Unlike STEP, it does not cause the program to pause before executing each statement. This parameter is very useful for debugging purposes. To turn TRACE off, you must interrupt BR (Ctrl-A) and use GO RUN to continue execution.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;PRINT&amp;quot; &#039;&#039;&#039;||sends TRACE output to an attached printer.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;STEP&amp;quot; &#039;&#039;&#039;||pauses the program before each statement is executed. The line number field in the status line tells you the next line number to be executed. That line is executed and the next line number appears when &amp;lt;CR&amp;gt; is pressed. STEP is useful for debugging of programs, as it allows you to closely follow the program&#039;s flow.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|width=&amp;quot;10%&amp;quot;|&#039;&#039;&#039;&amp;quot;PROC&amp;quot; &#039;&#039;&#039;||accept input from the procedure file that contains this command, for all INPUT, LINPUT, and RINPUT statements. Normally the input for these statements would come from the keyboard. The RUN PROC option has no effect on INPUT FIELDS or RINPUT FIELDS statements.&lt;br /&gt;
|-valign=&amp;quot;top&amp;quot;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If you wish to start running the program from a point other than the beginning, you can use &amp;quot;line-num&amp;quot; to specify the first line of execution.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;File_ref saves to a file all program output that normally goes to the printer. Use of one redirection arrow (&amp;gt;) causes output to overwrite the contents of the specified file. Two redirection arrows (&amp;gt;&amp;gt;) cause the output to be appended to the end of the specified file.&lt;br /&gt;
&lt;br /&gt;
The &amp;gt;file-ref can also redirect printer output to other communications devices. To send output to the screen, for example, use:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;CON:&lt;br /&gt;
&lt;br /&gt;
To send it to whatever device is connected to the first communications port, use:&lt;br /&gt;
&lt;br /&gt;
 RUN &amp;gt;AUX:&lt;br /&gt;
&lt;br /&gt;
These strings must be translated with the SUBSTITUTE specification for BR or Linux versions. BR sends all program results to the specified file-ref location until you enter another RUN command.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
# BR closes a procedure before executing the last line. Thus when RUN file-ref is included as the last line, there is a clean trade-off in the number of open files on the system. But RUN PROC cannot be used as the last line because the procedure will be closed before the program starts to look for data in the procedure (an easy solution is to add a blank line or comment line after the RUN PROC command).&lt;br /&gt;
# The use of RUN PROC does not alter the forward slash&#039;s (/) capability of terminating INPUT, LINPUT and RINPUT statements. See the INPUT statement for additional information.&lt;br /&gt;
# RUN &amp;gt;file-ref remains in effect until another RUN command is issued. Output redirected to a file is not accessible until the file is closed by another RUN command.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Program Management Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Proc&amp;diff=11444</id>
		<title>Proc</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Proc&amp;diff=11444"/>
		<updated>2024-02-28T16:28:17Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* See Also */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The &#039;&#039;&#039;Proc&#039;&#039;&#039; [[command]] opens and begins execution of a [[Procedure|procedure]] file.&lt;br /&gt;
&lt;br /&gt;
==Comments and Examples==&lt;br /&gt;
&lt;br /&gt;
BR automatically closes the most recently executed, active procedure file before opening and executing the procedure file specified with the PROC command. If a procedure is executed from a sub-procedure, the calling sub-procedure is canceled, but its parent remains active.&lt;br /&gt;
&lt;br /&gt;
The following command causes the procedure file PROCNAME, located on drive B, to be opened and executed:&lt;br /&gt;
&lt;br /&gt;
 PROC B:PROCNAME&lt;br /&gt;
&lt;br /&gt;
The next command starts execution of the procedure file START. The asterisk prevents F2 logging of the commands within START.&lt;br /&gt;
&lt;br /&gt;
 PROC *START&lt;br /&gt;
&lt;br /&gt;
==Syntax==&lt;br /&gt;
 PROC {[[ECHO]]|[[NOECHO]]|&amp;lt;[[Procedure files|proc name]]&amp;gt;|*&amp;lt;[[Procedure files|proc name]]&amp;gt;}&lt;br /&gt;
&lt;br /&gt;
[[Image:Proc.png]]&lt;br /&gt;
&lt;br /&gt;
==Defaults==&lt;br /&gt;
&lt;br /&gt;
# Log procedure commands for recall by the F2 key&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
The &#039;&#039;&#039;ECHO&#039;&#039;&#039; parameter reverses the affects of the PROC NOECHO command.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;NOECHO&#039;&#039;&#039; parameter prevents procedure lines from being displayed on the screen as they are executed. This command will apply to all procedures until PROC ECHO is entered or until Business Rules is exited.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;procedure file name&#039;&#039;&#039; parameter identifies the procedure file to be opened and executed. When &#039;&#039;&#039;procedure file name&#039;&#039;&#039; is preceded by an asterisk &#039;&#039;&#039;*&#039;&#039;&#039;, command logging will be turned off. Thus pressing of the F2 key will recall the PROC command, but not the commands in the procedure.&lt;br /&gt;
&lt;br /&gt;
==Technical Considerations==&lt;br /&gt;
&lt;br /&gt;
# If an error occurs while PROC NOECHO is active, F2 and F3 can be used to recall and display the error-causing command.&lt;br /&gt;
# ALERT commands which are executed while PROC NOECHO is active will not be displayed on the screen. The operator must press F2 or F3 to view the message. Use of PROC ECHO before and PROC NOECHO after the ALERT command is recommended to avoid this situation.&lt;br /&gt;
# Each open procedure file counts as one open file against the operating system limit on open files.&lt;br /&gt;
# Business Rules closes a procedure before executing the last line in the procedure. This eliminates procedure stacking (and an added open file) in situations such as when one procedure ends with a SUBPROC command to call another procedure, or when RUN is the last command in a procedure, or when a procedure has only one command.&lt;br /&gt;
# As the procedure file is closed before the last command is executed, the last command cannot be a SKIP to branch back to an earlier part of the procedure. If you want to end with an option to branch backward, you must add another line (such as a blank line or a remark) to keep the procedure file open.&lt;br /&gt;
# {{:$$$}}&lt;br /&gt;
&lt;br /&gt;
===See Also===&lt;br /&gt;
[[CLEAR]] PROC command.&lt;br /&gt;
&lt;br /&gt;
[[ProcIn|PROCIN]] system function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Commands]]&lt;br /&gt;
[[Category:Procedure Commands]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=CurRow&amp;diff=11443</id>
		<title>CurRow</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=CurRow&amp;diff=11443"/>
		<updated>2024-02-05T05:32:12Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt; CURROW&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;CURROW&#039;&#039;&#039; internal function returns the cursor&#039;s row from last [[Input Fields]] or [[RInput Fields]] statement.&lt;br /&gt;
&lt;br /&gt;
CURROW and [[CURCOL]] return the row or column (within a window) of the first character position of the control being exited. However, if the control is a LIST or GRID then CURROW and CURCOL identify the cell that was exited relative to the control itself (see [[Grid and List]]). (OPTION 59 provides the exact position of the mouse click.) &lt;br /&gt;
&lt;br /&gt;
===Comments and Examples===&lt;br /&gt;
&lt;br /&gt;
 00100 DIM ALL$*1920&lt;br /&gt;
 00110 INPUT FIELDS &amp;quot;1,1,c 1920,u&amp;quot;: ALL$&lt;br /&gt;
 00120 PRINT &amp;quot;Cursor ended on ROW&amp;quot;; CURROW&lt;br /&gt;
 00130 PRINT &amp;quot;Cursor ended on COLUMN&amp;quot;; CURCOL&lt;br /&gt;
 00140 PRINT &amp;quot;Cursor ended on FIELD&amp;quot;; CURFLD&lt;br /&gt;
&lt;br /&gt;
In the sample program above, the entire screen is treated as one 1920-character input field. The operator can move the cursor to any of the 24 rows or any of the 80 columns. After the operator hits the &amp;lt;ENTER&amp;gt; key, line 120 will print the row number containing the cursor when input was ended. Also, line 130 will print the column number. Line 140 will print field 1 because there is only one large field in this example.&lt;br /&gt;
&lt;br /&gt;
===Related Functions===&lt;br /&gt;
&lt;br /&gt;
*[[CurCol]]&lt;br /&gt;
*[[CurFld]]&lt;br /&gt;
*[[CurWindow]]&lt;br /&gt;
*[[NxtRow]]&lt;br /&gt;
*[[NxtCol]]&lt;br /&gt;
*[[Next (Internal Function)]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Internal Functions]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11442</id>
		<title>Grid and List</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11442"/>
		<updated>2024-02-05T05:25:45Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* FKey Processing */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This discussion assumes the reader has a clear understanding of [[INPUT FIELDS]] and [[PRINT FIELDS]] processing with simple fields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grid&#039;&#039;&#039; and &#039;&#039;&#039;List&#039;&#039;&#039; (a.k.a. &#039;&#039;&#039;ListView&#039;&#039;&#039;) are [[Two dimensional controls]], which means that they contain rows and columns. GRIDs are normally used for data entry, while LISTs are used only for selection. In Business Rules, outputting to these controls is done with [[Print Fields]] and inputting from them is done with [[Input Fields]]. Each field specification pertains to one [[Control]]. However, a single FIELD specification can pertain to a parenthesized group of arrays. When using Grids and Lists, first the header must be populated, and then the data within the rows. &lt;br /&gt;
&lt;br /&gt;
The [[INPUT FIELDS]] specification states Grid or List as the field type. The display area is specified in terms of rows and columns separated by a slash. The following parameter, instead of being leading field attributes, is a secondary keyword indicating the type (CNT/SUB/CELL/ROWCNT/ROWSUB/ROW) of output or input operation to be performed. The next parameter further qualifies the IO operation (CHG/SEL/ALL/CUR/NEXT). Normally this trailing attribute is an [[FKEY]] value which is shifted right one parameter in this context. &lt;br /&gt;
&lt;br /&gt;
It is useful to work with [[2D Controls]] in terms of rows versus cells, particularly when the columns are dissimilar. A complete set of row oriented parameters are provided for that purpose. Output operations support the mass populating of 2D controls row by row. And input operations can be row oriented as well. The keywords associated with row processing are R, ROWCNT, ROWSUB, and ROW. &lt;br /&gt;
&lt;br /&gt;
RINPUT does not work with 2D controls because the output statements are somewhat different from the input statements. The output consists of setting up the columns, including providing the column headings, and populating the control with data. The input consists of identifying what has changed in a manner that enables selective data retrieval and corresponding file updating. &lt;br /&gt;
&lt;br /&gt;
If the user clicks on a column heading the GRID or LIST will be sorted on that column. Sorting is done in terms of rows. Such sorting of these controls has no affect on the BR program. The information returned to BR will be as though no sorting were performed. If a control&#039;s population is increased by populating with the plus (+) flag, the control will be resequenced back to its original order before the data is added. One way a program can restore the original displayed order of a GRID or LIST is to populate it incrementally (+) with no data. As of version 4.1 and higher, if a GRID input is attempted on a protected field, BR issues a Bell. &lt;br /&gt;
&lt;br /&gt;
Shift+PgUp and Shift+PgDn selects within a List/Grid. &lt;br /&gt;
&lt;br /&gt;
Grids and lists are compatible with the [[FILTER]] field, which works like a search bar. &lt;br /&gt;
&lt;br /&gt;
In GRIDs and LISTs only, string arrays may be used to process numeric values. BR automatically performs [[VAL]] and [[STR]] conversions as needed.  If string data is passed to a numeric field type such as N or DATE then it is automatically converted to numeric form for internal processing (4.2).&lt;br /&gt;
&lt;br /&gt;
As of Business Rules! versions [[4.3]]+, arrays are automatically resized when receiving data from 2D INPUT operations. This also applies to grouped arrays. Automatic resizing only applies to one dimensional arrays and does not occur when INPUTing into two dimensional arrays. For example, where all arrays are one dimensional and may have the incorrect number of elements:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, ALL&amp;quot;&amp;amp;nbsp;: ( MAT Array1$,&amp;lt;span style=&amp;quot;font-family: monospace;&amp;quot;&amp;gt; &amp;lt;/span&amp;gt;MAT Array2$, MAT Array3, MAT Array4, MAT Array5$ )  &lt;br /&gt;
&lt;br /&gt;
====FKey Processing====&lt;br /&gt;
&lt;br /&gt;
An [[FKey]] value can be associated with a LIST or GRID control by specifying the FKey number during either output or input operations. Once an Fkey value is specified, the control retains the setting until it is reset. An FKey value can be cleared by specifying an Fkey value of minus one (-1). &lt;br /&gt;
&lt;br /&gt;
When a LIST or GRID has an FKey value set processing is dependent on whether or not the 2D control is being processed by an INPUT FIELDS statement: &lt;br /&gt;
&lt;br /&gt;
*Displayed but inactive- Single clicking any cell produces the FKey interrupt. &lt;br /&gt;
*Active (participating in Input Fields)- Double clicking any cell produces an FKey completion.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
With regard to simple field specifications in PRINT FIELDS or INPUT FIELDS statements, [[CURROW]] and [[CURCOL]] identify the character position where the cursor was when FIELDS processing is completed. However, when the most recent field processed is a [[2D control]] then [[CURROW]] and [[CURCOL]] results have different meanings:&lt;br /&gt;
&lt;br /&gt;
When FIELDS processing ends and control is returned to the program while the &#039;current&#039; control is of type LIST or GRID, CURROW and CURCOL are set to the current cell row and column within the 2D control instead of the character position relative to the window.&lt;br /&gt;
&lt;br /&gt;
====GRID CURSOR MOVEMENT====&lt;br /&gt;
When field + or - is keyed BR always returns fkey values of 114 or 115 in both navigation and edit mode and for any type of data. &lt;br /&gt;
(This assumes the X attribute or some other attribute returns control to the program.)&lt;br /&gt;
&lt;br /&gt;
Default cursor positioning for Field +/- keys is to perform a down arrow operation, and the Enter key defaults to NONE (no movement). &lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- forces the signing of a numeric field, whether or not the field type is PIC.&lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- also right truncates any character or numeric data before exiting the field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A Config statement can be used to override (control) grid cursor movement. When this is used, both Field +/- and Enter produce the same cursor movement:&lt;br /&gt;
&lt;br /&gt;
 GRID_CURSOR_MOVE  DOWN | RIGHT | NONE | DEFAULT&lt;br /&gt;
&lt;br /&gt;
This determines the field cursor position after keying Enter or Field +/-. Both navigation and edit mode produce the same resulting cursor position.&lt;br /&gt;
&lt;br /&gt;
====Restoring a User Sorted 2D Control====&lt;br /&gt;
&lt;br /&gt;
In versions 4.3 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns. &lt;br /&gt;
&lt;br /&gt;
==== A multi column LISTview  ====&lt;br /&gt;
&lt;br /&gt;
 01000 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS,[HDRS][,fkey]&amp;quot;: (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Widths are expressed in character positions.&lt;br /&gt;
&lt;br /&gt;
The [HDRS] notation refers to an optional CONFIG ATTRIBUTE HDRS specification for setting the appearance of the header row. In this case the  brackets [] are required and the term HDRS may be any bracketed attribute name. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT HEADINGS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the titles that will appear at the top of each column in the List or Grid. The format of this row may be specified by an optional parameter following HEADERS in the above example.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT WIDTHS&#039;&#039;&#039; &lt;br /&gt;
| Specifies the number of characters in each column. For example if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT FORMS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the display characteristics of each column such as &amp;quot;C 12&amp;quot; or &amp;quot;PIC(z,zzz,zz#.##-)&amp;quot;. A &amp;quot;P&amp;quot; following the display parameter will cause the field to be protected and no data entry will be allowed in GRID mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; The field form array elements may also include a trailing comma followed by field attributes (e.g. color) that pertain to the column. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80, = [,fkey]&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt.&lt;br /&gt;
&lt;br /&gt;
==== Reading a Listview or Grid  ====&lt;br /&gt;
When using INPUT FIELDS to read from a 2D control, the leading attributes specification states the type of read operation and the trailing attributes specification is the type of cell or row selection to be performed. The third parameter can optionally specify NOWAIT or an FKEY value. If it is an FKEY value, it signifies that an FKEY event should be triggered when, in navigation mode, a selection is made by double clicking or pressing the Enter key.&lt;br /&gt;
 &lt;br /&gt;
===== Syntax  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Grid.png|950px]]&lt;br /&gt;
&lt;br /&gt;
=====Parameters=====&lt;br /&gt;
Quotation marks must surround the specifications, and individual parts must be separated by commas.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Row&#039;&#039;&#039; and &#039;&#039;&#039;Column&#039;&#039;&#039; specify the space where the grid or list begins.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;List&#039;&#039;&#039; or &#039;&#039;&#039;Grid&#039;&#039;&#039;, followed by rows and columns separated by a slash determine how big the list or grid is going to be. The main difference between a list and grid is that lists are for selection only while information can be added to grids directly. &lt;br /&gt;
&lt;br /&gt;
The following &#039;&#039;&#039;Read Types&#039;&#039;&#039; are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowCnt&#039;&#039;&#039; &lt;br /&gt;
| The number of rows specified.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowSub&#039;&#039;&#039; &lt;br /&gt;
| The subscripts of the specified rows.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Row&#039;&#039;&#039; &lt;br /&gt;
| Read all cells in each specified row.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Colcnt&#039;&#039;&#039; &lt;br /&gt;
|The number of columns established by the header arrays. e.g. INPUT FIELDS &amp;quot;row,col,LIST rows/cols, COLCNT, ALL&amp;quot; : numeric-variable&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sort_Order&#039;&#039;&#039; &lt;br /&gt;
|(4.3+) Provides a value of zero for each unsorted column and gives the ascending sequence of column sorts that have occurred. If a column has been reversed (double sorted) it&#039;s value will be negative. The selection typed used must be ALL. For example: INPUT FIELDS &amp;quot;row,col,GRID rows/cols, SORT_ORDER, ALL&amp;quot; : Mat NumArray, with the following history of sorting a four column GRID:&lt;br /&gt;
 column 1 (descending most recent)&lt;br /&gt;
 column 2 (ascending first sorted)&lt;br /&gt;
 column 3 (not sorted)&lt;br /&gt;
 column 4 (sorted ascending)&lt;br /&gt;
&lt;br /&gt;
SORT_ORDER would return-&lt;br /&gt;
 array(1) -&amp;gt;  -1&lt;br /&gt;
 array(2) -&amp;gt;   3&lt;br /&gt;
 array(3) -&amp;gt;   0&lt;br /&gt;
 array(4) -&amp;gt;   2&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;HEADERS&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) The read operation returns the original PRINT FIELDS HEADER values. For example:  INPUT FIELDS &amp;quot;row,col,LIST rows/cols, HEADERS,ALL,NOWAIT&amp;quot; : (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$) The selection type used must be ALL.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MASK&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) MASK can be used with Grids and Lists. As a READ type, this reads the display mask setting, including listviews that have been displayed according to a [[filter]] or filterbox. For example: INPUT FIELDS &amp;quot;row,col,LIST rows/cols,MASK [,NOWAIT]&amp;quot; : mask_array. The mask array affects only the user presentation and not the data. Use RANGE processing or the CHG selection type to selectively read from a 2D control.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These Read Types are valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cnt&#039;&#039;&#039; &lt;br /&gt;
| Specify the number of cells specified (see selection types below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sub&#039;&#039;&#039; &lt;br /&gt;
| Read the Cell Subscript Values (see example below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell&#039;&#039;&#039; &lt;br /&gt;
| Read each cell specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For the &amp;quot;Sel-type&amp;quot; parameter, the following selection types are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sel&#039;&#039;&#039; &lt;br /&gt;
| Read one or more selected items.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;SelOne&#039;&#039;&#039; &lt;br /&gt;
| Select only one item.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;All&#039;&#039;&#039; &lt;br /&gt;
| Read all items in the control (except headings).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cur&#039;&#039;&#039; &lt;br /&gt;
| Current cell or row number.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Next (4.2+)&#039;&#039;&#039; &lt;br /&gt;
| The cell the cursor is going to next if the user moved it using an arrow or a mouse click.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Range&#039;&#039;&#039;&lt;br /&gt;
|Specifies which portion of a 2D control is to be input. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell_Range&#039;&#039;&#039;&lt;br /&gt;
|A special type of output range. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This Selection Type is valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Chg&#039;&#039;&#039; &lt;br /&gt;
| All items changed since the last &#039;=&#039; populate or the last CHG retrieval of cell/row contents.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039; is an optional parameter to help the read. As of 4.3 it can be [[DISPLAYED_ORDER]]. #[[PIC]] or #[[FMT]] could be used. #PIC and #FMT allow numeric data from a string to be used. For example: &#039;&#039;&#039;DISPLAYED_ORDER&#039;&#039;&#039; Indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program (as of version 4.30). This reads the original row subscripts for all rows - in their present order - and only works with the ALL selection type.&lt;br /&gt;
&lt;br /&gt;
GRIDLINES makes LIST controls look like GRIDs with respect to the display of data (column and row separators).&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80,GRIDLINES&amp;quot;: 1 | 0 (on or off)&lt;br /&gt;
&lt;br /&gt;
The leading attribute values &amp;quot;^select&amp;quot; or &amp;quot;^deselect&amp;quot; may be specified to allow the pre-selection of GRID / LIST Elements: &lt;br /&gt;
 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,^select ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Fkey&#039;&#039;&#039; and &#039;&#039;&#039;Nowait&#039;&#039;&#039; parameters are optional. FKEY means that an FKEY event should be triggered when a selection is made by double clicking or pressing the Enter key, in navigation mode. Nowait simply means that it does not wait for user input.&lt;br /&gt;
&lt;br /&gt;
Following the ending quotation mark, a colon precedes the name of the I/O List.&lt;br /&gt;
&lt;br /&gt;
====DISPLAYED ORDER &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039;==== &lt;br /&gt;
&lt;br /&gt;
As of 4.30, [[DISPLAYED ORDER]] - indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program, for example:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROWSUB, ALL, DISPLAYED_ORDER, NOWAIT&amp;quot;: numeric-array &lt;br /&gt;
&lt;br /&gt;
This reads the original row subscripts for all rows in their present order. This qualifier works only with the ALL selection type. It may be used in conjunction with other qualifiers such as FKEY.&lt;br /&gt;
&lt;br /&gt;
==== Examples  ====&lt;br /&gt;
&lt;br /&gt;
===== LIST  =====&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL,FKEY&amp;quot;: avail_rows&amp;amp;nbsp;! selected row cnt&lt;br /&gt;
 00220&amp;amp;nbsp;! next INPUT operation does not wait for operator&lt;br /&gt;
 00230 MAT subscr(avail_rows)         &amp;amp;nbsp;! redimension with number of selected rows&lt;br /&gt;
 00240 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWSUB,SEL,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
&lt;br /&gt;
===== Uniform GRID  =====&lt;br /&gt;
Contains one data array and multiple columns&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CNT,CHG&amp;quot;: cells     &amp;amp;nbsp;! # of changed cells&lt;br /&gt;
 00220 MAT subscr(cells)                                        &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,SUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT data$(cells)                                         &amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CELL,CHG,NOWAIT&amp;quot;: MAT data$&amp;amp;nbsp;! read changes&lt;br /&gt;
&lt;br /&gt;
===== Row Oriented GRID  =====&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWCNT,CHG&amp;quot;: rows  &amp;amp;nbsp;! # of changed rows&lt;br /&gt;
 00220 MAT subscr(rows)                                  &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWSUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT NAME$(rows)&amp;amp;nbsp;: MAT CITY$(rows)&amp;amp;nbsp;: MAT AGE(rows)&amp;amp;nbsp;: MAT WEIGHT(rows)&amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROW,CHG,NOWAIT&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)  &amp;amp;nbsp;! read changed rows&lt;br /&gt;
&lt;br /&gt;
This brings us to the question of what is to be done with the information after it has been read. If it is to be stored in a file, then we should have included a hidden column with master file record numbers of the data in each row. This would support looping through the input array and rewriting the changed data. &lt;br /&gt;
&lt;br /&gt;
While LIST subscripts are expressed in terms of rows, GRID subscripts may be either. In a four column five row GRID, the cell at row three column two has a subscript of ten.&lt;br /&gt;
&lt;br /&gt;
==== Cell Subscript Values of a 5 x 4 GRID  ====&lt;br /&gt;
&lt;br /&gt;
 1   2   3   4&lt;br /&gt;
 5   6   7   8&lt;br /&gt;
 9  10  11  12&lt;br /&gt;
 13 14  15  16&lt;br /&gt;
 17 18  19  20&lt;br /&gt;
&lt;br /&gt;
==== Sample Program====&lt;br /&gt;
The following example shows use of LIST and GRID controls. There are seven parts in this program. &lt;br /&gt;
&lt;br /&gt;
The first part creates a LIST with 2 columns and 3 rows. On lines 300 - 500, column headings, widths, and form specifications are assigned to the corresponding matrices. Lines 600 and 700 place data into the matrices to be printed in the LIST. The HEADERS operation on line 800 sets the column headings, widths and form specs. Line 900 populates the LIST by row, which is the default. &lt;br /&gt;
&lt;br /&gt;
 00100 dim HEADINGS$(2), WIDTHS(2), FORMS$(2), NAMES$(3)*28, CITIES$(3)*18, DATA$(1)*80, SUBSCR(1)&lt;br /&gt;
 00200 print NEWPAGE&lt;br /&gt;
 00300 let HEADINGS$(1)=&amp;quot;Name&amp;quot;: let HEADINGS$(2)=&amp;quot;City&amp;quot;&lt;br /&gt;
 00400 let WIDTHS(1)=30&amp;amp;nbsp;: let WIDTHS(2)=20&lt;br /&gt;
 00500 let FORMS$(1)=&amp;quot;CC 28&amp;quot;&amp;amp;nbsp;: let FORMS$(2)=&amp;quot;CC 18&amp;quot;&lt;br /&gt;
 00600 let NAMES$(1)=&amp;quot;Stalin&amp;quot;&amp;amp;nbsp;: let NAMES$(2)=&amp;quot;Napoleon&amp;quot;&amp;amp;nbsp;: let NAMES$(3)=&amp;quot;Roosevelt&amp;quot;&lt;br /&gt;
 00700 let CITIES$(1)=&amp;quot;Moscow&amp;quot;&amp;amp;nbsp;: let CITIES$(2)=&amp;quot;Paris&amp;quot;&amp;amp;nbsp;: let CITIES$(3)=&amp;quot;Washington&amp;quot;&lt;br /&gt;
 00800 print fields &amp;quot;1,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 00900 print fields &amp;quot;1,1,list 8/60,=R&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
 01000 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at the end of list&amp;quot;&lt;br /&gt;
 01100 let KSTAT$(1)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output1.jpg]] &lt;br /&gt;
&lt;br /&gt;
The second part of the program demonstrates the use of the primary flag +, which adds to the end of any previously populated data. Line 01200 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 01300 places new data into the matrices to be printed in the LIST.&amp;lt;br&amp;gt; Line 01400 adds the new data to the LIST. &lt;br /&gt;
&lt;br /&gt;
 01200 mat NAMES$(UDIM(NAMES$)+1)&amp;amp;nbsp;: mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 01300 let NAMES$(4)=&amp;quot;Churchill&amp;quot;&amp;amp;nbsp;: let CITIES$(4)=&amp;quot;London&amp;quot;&lt;br /&gt;
 01400 print fields &amp;quot;1,1,list 8/60,+&amp;quot;: (MAT NAMES$(4:4),MAT CITIES$(4:4))&lt;br /&gt;
 01500 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Select rows to be read into a matrix&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output2.jpg]] &lt;br /&gt;
&lt;br /&gt;
The third part of the program demonstrates use of read types ROWSUB and ROWCNT and selection type SEL. Line 1600 inputs the number of selected rows into AVAIL_ROWS. The user may select rows using the mouse or the keyboard and SHIFT and CTRL keys. Line 1700 redimensions matrix SUBSCR to the number of selected rows. Line 1800 inputs the subscripts of the selected rows into matrix SUBSCR. Line 1900 performs a HEADERS operation for a new LIST using the same matrices as were used in the previous LIST. Line 2000 populates the first row of the new LIST with the data from the first selected row from the previous LIST using the primary flag =. If user selects more than one row, then lines 2100 - 2500 add the data from the selected rows using the primary flag +. &lt;br /&gt;
&lt;br /&gt;
 01600 input fields &amp;quot;1,1,list 8/60,ROWCNT,SEL&amp;quot;: AVAIL_ROWS&lt;br /&gt;
 01700 mat SUBSCR(AVAIL_ROWS)&lt;br /&gt;
 01800 input fields &amp;quot;1,1,list 8/60,rowsub,sel,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 01900 print fields &amp;quot;12,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 02000 print fields &amp;quot;12,1,list 8/60,=&amp;quot;: (MAT NAMES$(SUBSCR(1):SUBSCR(1)),MAT CITIES$(SUBSCR(1):SUBSCR(1)))&lt;br /&gt;
 02100 if UDIM(SUBSCR) &amp;amp;gt; 1 then&lt;br /&gt;
 02200   for I = 2 to UDIM(SUBSCR)&lt;br /&gt;
 02300     print fields &amp;quot;12,1,list 8/60,+&amp;quot;: ( MAT NAMES$(SUBSCR(I):SUBSCR(I)),MAT CITIES$(SUBSCR(I):SUBSCR(I)))&lt;br /&gt;
 02400   next I&lt;br /&gt;
 02500 end if&lt;br /&gt;
 02600 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at beginning of list&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output3.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fourth part of the program demonstrates the use of the primary flag -, which inserts in the beginning of any previously populated data. Line 2800 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 2900 places new data into the matrices to be printed in the LIST. Line 3000 adds the new data to the beginning of the LIST ahead of previously populated data. &lt;br /&gt;
&lt;br /&gt;
 02700 let KSTAT$(1)&lt;br /&gt;
 02800 mat NAMES$(UDIM(NAMES$)+1): mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 02900 let NAMES$(5)=&amp;quot;Castro&amp;quot;&amp;amp;nbsp;:! let CITIES$(5)=&amp;quot;Havana&amp;quot;&lt;br /&gt;
 03000 print fields &amp;quot;1,1,list 8/60,-&amp;quot;: (MAT NAMES$(5:5),MAT CITIES$(5:5))&lt;br /&gt;
 03100 print fields &amp;quot;9,1,C 60&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to populate list by column&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output4.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fifth part of the program, more specifically, Line 3300, demonstrates the use of the secondary flag C to populate the LIST by column. The primary flag = is also used in order to replace any previously populated data. &lt;br /&gt;
&lt;br /&gt;
 03200 let KSTAT$(1)&lt;br /&gt;
 03300 print fields &amp;quot;1,1,list 8/60,=C&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output5.jpg]] &lt;br /&gt;
&lt;br /&gt;
The sixth part of the program creates a GRID. The HEADERS operation on line 3600 uses the same HEADINGS$, WIDTHS, and FORMS$ as the previously constructed LISTs. Line 3700 sets CURFLD to be on the sixth cell of the GRID (this is discussed in the next section). Line 3800 populates the GRID. The user may change the contents of the cells. &lt;br /&gt;
&lt;br /&gt;
 03400 print fields &amp;quot;23,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to continue&amp;quot;: let KSTAT$(1)&lt;br /&gt;
 03500 print NEWPAGE&lt;br /&gt;
 03600 print fields &amp;quot;1,1,grid 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 03700 let CURFLD (1,6)&lt;br /&gt;
 03800 print fields &amp;quot;1,1,grid 8/60,=&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6.jpg]] &lt;br /&gt;
&lt;br /&gt;
The seventh part of the program demonstrates the use of read types CNT, CELL, and SUB and selection type CHG. Line 3900 counts the number of changed cells and inputs that number into variable CELLS. Line 4000 redimensions the matrix SUBSCR to the number of changed cells. Line 4100 inputs the changed cells into SUBSCR. Line 4200 redimensions the matrix DATA$.  Line 4300 inputs the subscripts of the changed cells into matrix DATA$. Line 4400 prints DATA$. &lt;br /&gt;
&lt;br /&gt;
 03900 input fields &amp;quot;1,1,grid 8/60,cnt,chg&amp;quot;: CELLS&lt;br /&gt;
 04000 mat SUBSCR(CELLS)&lt;br /&gt;
 04100 input fields &amp;quot;1,1,grid 8/60,sub,chg,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 04200 mat DATA$(CELLS)&lt;br /&gt;
 04300 input fields &amp;quot;1,1,grid 8/60,cell,chg,nowait&amp;quot;: MAT DATA$&lt;br /&gt;
 04400 print MAT DATA$&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6b.jpg]]&lt;br /&gt;
&lt;br /&gt;
===== Output of line 04400  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output7.jpg]]&lt;br /&gt;
&lt;br /&gt;
=== Displaying a List or Grid (Output Operations) ===&lt;br /&gt;
[[file:Grid2.png|900px]]&lt;br /&gt;
&lt;br /&gt;
To display a listview or a grid, you must set the headers first, using a special PRINT FIELDS operation.&lt;br /&gt;
&lt;br /&gt;
====HEADERS====&lt;br /&gt;
The HEADERS operation sets the column headings and widths. The corresponding input/output list value must be a parenthesized group of three arrays, for example: &lt;br /&gt;
&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$)&lt;br /&gt;
      - or -&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS,MAT FIELD_FORMS$)&lt;br /&gt;
&lt;br /&gt;
The [hdrs] notation refers to an optional CONFIG ATTRIBUTE [HDRS] specification for setting the appearance of the header row. In this case the [] brackets are required. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
MAT HEADINGS$ Contains the column titles that will be displayed at the top of each column. The font, color and shading of these titles can be set through the [HDRS] or similar substitution attribute. &lt;br /&gt;
&lt;br /&gt;
MAT WIDTHS specifies DISPLAYED Column Widths and is expressed as the number of character positions occupied by each column. Scrollbars are provided as needed to honor overall control size specified in the FIELDS specification. &lt;br /&gt;
&lt;br /&gt;
For example, if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area. &lt;br /&gt;
&lt;br /&gt;
Displayed widths of zero characters are allowed. This enables the use of hidden columns for storing things like record numbers and record keys. &lt;br /&gt;
&lt;br /&gt;
MAT FIELD_FORMS$ provides the BR FORM for each column. e.g. C 15 stipulates a maximum field capacity of 15. The actual displayed length is a function of the grid size and the column relative width. &lt;br /&gt;
&lt;br /&gt;
The field form array elements may also include a comma followed by leading field attributes (e.g. color) pertaining to the column.  &lt;br /&gt;
&lt;br /&gt;
The number of columns must be set with HEADERS prior to Populating a control (loading data into it).&lt;br /&gt;
&lt;br /&gt;
====MASKing====&lt;br /&gt;
&lt;br /&gt;
As of 4.3, LIST and GRID support the MASK operation, both in READs and Populating, as seen in this section. For example:&lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;row,col,LIST rows/cols,MASK&amp;quot; :  mask_array&lt;br /&gt;
&lt;br /&gt;
This restricts the rows (for both LIST and GRID) previously displayed to those corresponding to a “true” value in mask_array. A true value is represented in a numeric array as a value greater than zero. Negative values are not allowed in mask arrays. A string mask array may also be used with “T” and “F” values. The MASK stays in effect until 1) a new MASK is specified or 2) the contents of the control are changed with PRINT ( &amp;lt;nowiki&amp;gt;=, +, -,&amp;lt;/nowiki&amp;gt; see primary flags below).  Also, the mask array affects only the user presentation, not the result set. &lt;br /&gt;
&lt;br /&gt;
====Populating====&lt;br /&gt;
&lt;br /&gt;
The populate operation loads data into the control. In the following example four columns are loaded: &lt;br /&gt;
&lt;br /&gt;
 03010 PRINT FIELDS &amp;quot;10,20,LIST 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)&lt;br /&gt;
      - or -&lt;br /&gt;
 03020 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt. &lt;br /&gt;
&lt;br /&gt;
Permissible leading attribute values are:&lt;br /&gt;
&lt;br /&gt;
===== Primary Flags  =====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;=&#039;&#039;&#039; &lt;br /&gt;
| Replace any previous data&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;+&#039;&#039;&#039; &lt;br /&gt;
| Add to any previously populated data (this allows loading in chunks)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;-&#039;&#039;&#039; &lt;br /&gt;
| Insert data ahead of previously populated data (4.16+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Secondary Flags  ====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;R&#039;&#039;&#039; &lt;br /&gt;
| Load one row at a time (the default - use grouped IO parens)&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;C&#039;&#039;&#039; &lt;br /&gt;
| Load one column at a time - This is for loading multiple columns of the same data type&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;L&#039;&#039;&#039; &lt;br /&gt;
| Provide the FKEY (see INPUT below) or Enter interrupt if the user presses up arrow or page up in the first field, or down arrow or page down in the last field. Note that this is not specified in the individual field leading attributes.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;S&#039;&#039;&#039; &lt;br /&gt;
| Single click to activate an Enter or FKEY event (otherwise a double click is required) (4.17+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Note that the following example will NOT work- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=C&amp;quot;: MAT NAME$,MAT CITY$,MAT AGE,MAT WEIGHT&lt;br /&gt;
&lt;br /&gt;
The reason is that only MAT NAME$ will reach the list. MAT CITY$, MAT AGE and MAT WEIGHT will be associated with subsequent fields. Multiple arrays provided to a single control must be grouped with parentheses. &lt;br /&gt;
&lt;br /&gt;
Populating a two-dimensional object by row with grouped IO means NAME$(1) will go into (1,1), AGE(1) will go into (1,2) and WGT(1) will go into (1,3) and so on. If a single array (MAT DATA$) is specified instead of a group, MAT DATA$ is applied horizontally instead of vertically. So DATA$(1) - DATA$(3) will be the first row and DATA$(4) - DATA$(6) will be the next row and so on. &lt;br /&gt;
&lt;br /&gt;
Populating a two dimensional grid by column with an array named DATA$ means that DATA$(1) goes in (1,1) and DATA$(2) goes in (2,1) and DATA$(3) goes in (3,1). Therefore the first however many values of DATA refer to the first column and the second however many values of DATA refer to the second column. So if there are 3 columns and UDIM(DATA$) = 90 then DATA$(1)-DATA$(30) is the first column, and DATA$(31) - DATA$(60) is the second column and DATA$(61) - DATA$(90) is the last column. &lt;br /&gt;
&lt;br /&gt;
For example, using the following information, each example demonstrates how the grid will be filled:&lt;br /&gt;
&lt;br /&gt;
MAT NAME$ = George, Peter, Tom &lt;br /&gt;
&lt;br /&gt;
MAT CITY$ = Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
MAT AGE$ = 42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
MAT WEIGHT$ = 180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Peter, Detroit, 23, 212 &lt;br /&gt;
&lt;br /&gt;
Tom, Denver, 35, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =C&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
As you can see using C with grouped arrays is counter-intuitive and doesn&#039;t fit well. C is most useful with a single array that should be loaded vertically down several columns. &lt;br /&gt;
&lt;br /&gt;
===== Grid Validation  =====&lt;br /&gt;
&lt;br /&gt;
GRIDs are now validated as each cell is exited instead of when control is passed to the BR program after all data is entered.&lt;br /&gt;
&lt;br /&gt;
===== Color and Font changes in Cells  =====&lt;br /&gt;
&lt;br /&gt;
The attributes that determine font, color and style in each cell can be set for an entire column by including these parameters in the heading FORM array. Individual cells can then be changed using a PRINT statement. &lt;br /&gt;
&lt;br /&gt;
The format of the print statement is &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT #WINNO, fields &amp;quot;2,2,LIST 10/60,ATTR&amp;quot;:(mat start, mat end, mat attribute$)&lt;br /&gt;
&lt;br /&gt;
With the following parameter descriptions: &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat start&#039;&#039;&#039; &lt;br /&gt;
| contains the cell number(s) where the attribute chain begins,&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat end&#039;&#039;&#039; &lt;br /&gt;
| contains the last cell number where the attribute applies&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat attribute$&#039;&#039;&#039; &lt;br /&gt;
| contains the attribute specification that should be applied to the cell range(s) specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If the 2d control is a GRID, then mat Start and mat End refer to starting and ending Cell Numbers. If the 2d control is a listview, then mat Start and mat End refer to starting and ending Row Numbers. &lt;br /&gt;
&lt;br /&gt;
The attributes specified for any COLUMN can be overridden on a cell basis by specifying the starting cell number, ending cell number, and the overriding attributes in three arrays that are printed to the grid window with the same grid specificatoins and the key word &amp;quot;ATTR&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
 02420 PRINT #BLISTWIN,FIELDS BLISTSPEC$&amp;amp;amp;&amp;quot;,ATTR&amp;quot;: (MAT BROWS,MAT BROWE,MAT BATT$)&lt;br /&gt;
&lt;br /&gt;
In the above example, BLISTWIN is the window number, BLISTSPEC$ is the grid specification (&amp;quot;GRID 10/40&amp;quot; for example), BROWS is an array holding the starting cell number, BROWE is an array holding the ending cell number, and BATT$ is an array holding the overriding attributes. In a list the attributes of the first cell in the row controls the appearance of the entire row.&lt;br /&gt;
&lt;br /&gt;
 01500 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The above example overrides the attributes of a range of cells/rows for a GRID/LIST display. This allows you to shade or otherwise alter the display of a range of cells / rows in a 2D control.&lt;br /&gt;
&lt;br /&gt;
====Aggregate Sorting====&lt;br /&gt;
BR supports aggregated sorting for LISTs and GRIDs. This means when clicking &lt;br /&gt;
on various column headings or programmatically sorting columns, fields of &lt;br /&gt;
equal values retain their previous order within their new groupings (4.2).&lt;br /&gt;
&lt;br /&gt;
==== Numeric Column Sorting  ====&lt;br /&gt;
&lt;br /&gt;
2D controls now facilitate numeric column sorting. This works well in conjunction with the new [[Date (Format Specification)|DATE]] field format (see release notes [[4.16]]) where the data is stored as day of century, but is displayed as a formatted date. It also works with all numeric columns.&lt;br /&gt;
&lt;br /&gt;
If a listview columns form spec if given as DATE(mm/dd/ccyy), for example, then any time that column is sorted, either as a result of the user clicking on the column heading, or the program giving the sort command (shown below), the dates are properly sorted even though they&#039;re displayed as mm/dd/ccyy. For this to work, the data has to be given in the julian days format, see the [[DAYS]] function for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In versions 4.2 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
To sort in reverse order, sort the column twice:&lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { same column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended in version 4.3 to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns.&lt;br /&gt;
&lt;br /&gt;
==== NOSORT for Columns  ====&lt;br /&gt;
As of 4.2, the &#039;&#039;&#039;NoSort&#039;&#039;&#039; parameter is used to prevent users from sorting columns of a Grid or List. &lt;br /&gt;
&lt;br /&gt;
For the statement: &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$) &lt;br /&gt;
&lt;br /&gt;
The field attribute &amp;quot;^nosort&amp;quot; appearing in the MAT FIELD_FORM$ prevents the sorting of a grid or listview in response to the user clicking on the corresponding column header. This does not prevent programs from sorting on those columns.&lt;br /&gt;
&lt;br /&gt;
==== Range Input  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are used in versions 4.3 and higher. In these examples BR will redimension the receiving arrays as needed: &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, CELL, RANGE&amp;quot; :&lt;br /&gt;
             start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This reads the specified range of cells. BR redimensions MAT Data$ as needed. Note that CELL may now be used with LIST. Previously, LISTs were only addressable by row. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot; :&lt;br /&gt;
             (start:=7), (end:=11), ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads the cells in rows 7 through 11. The receiving arrays are re-dimensioned as appropriate. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,GRID rows/cols, ROW, RANGE&amp;quot;:&lt;br /&gt;
             MAT start, MAT end, ( MAT Data1$, MAT Data2$, MAT Data3 )&lt;br /&gt;
&lt;br /&gt;
This reads one or more ranges of rows. &lt;br /&gt;
&lt;br /&gt;
A more detailed example of this is: &lt;br /&gt;
&lt;br /&gt;
 100 ! create and populate a LIST control &lt;br /&gt;
 200 MAT START(3) : MAT END(3)&lt;br /&gt;
 210 READ MAT START, MAT END&lt;br /&gt;
 220 DATA 7,21,33&lt;br /&gt;
 230 DATA 11,21,38&lt;br /&gt;
 240 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot;&amp;amp;nbsp;: MAT START,&lt;br /&gt;
            MAT END, ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads 12 rows of data ( row 7-11, row 21 and rows 33-38 ). &lt;br /&gt;
&lt;br /&gt;
==== Range Output  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions [[4.3]] and higher. By default, RANGE output refers to rows. The special keyword CELL_RANGE is used to denote the specification of cell ranges. Additionally, the use of scalars versus arrays for start and end values determines important characteristics of the output process. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Using Scalars For Range Specification &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,GRID 10/75, RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This replaces the values in rows numbered &#039;start&#039; through &#039;end&#039; with the data in MAT Data$. The size of MAT Data$ must be a multiple of the number of columns in the GRID or an error is generated. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$,                                   MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
This replaces the values in ROWs numbered &#039;start&#039; through &#039;end&#039; with the data from MATs NAME$, CITY$, AGE and WEIGHT. The data arrays must all be dimensioned the same. &lt;br /&gt;
&lt;br /&gt;
==== Insertion and Deletion with RANGE ====&lt;br /&gt;
&lt;br /&gt;
The number of rows being output do not need to match the number of rows being replaced. To delete a range of rows, output one or more grouped arrays with zero elements. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. Using the following statement, various scenarios are described. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays have been DIMed to nine elements&lt;br /&gt;
 Result- Nine rows replace five, and the total content of the control is expanded by 4 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays are DIMed to zero elements&lt;br /&gt;
 Result- Five rows are deleted, and the total size of the control is reduced by 5 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=0 (anything less than 7), and the arrays are DIMed to support three rows&lt;br /&gt;
 Result- Three rows are inserted ahead of row seven and the total content of the control is expanded by three rows&lt;br /&gt;
&lt;br /&gt;
 start=5000, end={any value}, the control only has 482 rows, and the source arrays are DIMed to support eleven rows&lt;br /&gt;
 Result- Eleven rows are appended to the end of the control and become rows 483 through 493.&lt;br /&gt;
&lt;br /&gt;
==== Outputting Ranges of Cells  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. &lt;br /&gt;
&lt;br /&gt;
Ranges of cells may be output in conjunction with the CELL_RANGE keyword. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, CELL_RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
                                - or -&lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
In this case, start and end specify cells instead of rows. If insertion or deletion is indicated by dimensioning the data arrays to greater or fewer elements than are being replaced, then the data must be a multiple of the number of columns. Insertion and deletion is only valid in terms of rows, even when cell subscripts are used to specify ranges. In such cases, if the cell subscripts are not on row boundaries, an error is generated. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, start, Data$&lt;br /&gt;
&lt;br /&gt;
In this example, the value in one cell is replaced with the content of a scalar.&lt;br /&gt;
&lt;br /&gt;
==== Using Arrays For Range Specification  ====&lt;br /&gt;
&lt;br /&gt;
If the start and end specifications are array denoting multiple ranges, there must be a one to one correspondence between the number of rows specified and those in the data. This method implies replacement only and insertion or deletion is not allowed. &lt;br /&gt;
&lt;br /&gt;
The data flow that this feature was designed to support is one where the user is presented with a LIST or GRID where multiple rows have been either selected or changed before returning control to the program and the program is responding by updating something on those rows. &lt;br /&gt;
&lt;br /&gt;
The program begins by presenting a 2D control to the user and reading the the control with type ROWSUB or SUB. Type SUB only works for GRIDs where all colmns have the same data type. Of course the subscripts are read into a numeric array which BR redimensions as appropriate. Then the program reads the changed or selected data with NOWAIT. (This resets the CHG flags in the control.) The program then changes either row (ROWSUB) or cell (SUB) data and outputs the results using the subscript array as both the start and end specification. Other scenarios are possible but this is the primary intended use. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher: &lt;br /&gt;
&lt;br /&gt;
  100 ! create and populate a GRID --&lt;br /&gt;
  200 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROWSUB,CHG&amp;quot;: MAT Rowsubs&lt;br /&gt;
         (Reading subscripts does not reset the CHG flags in the control.)&lt;br /&gt;
  210 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROW,CHG,NOWAIT&amp;quot;: ( MAT Data1$,&lt;br /&gt;
        MAT Data2, MAT Data3$ )&lt;br /&gt;
          BR redimensions the receiving arrays as needed.&lt;br /&gt;
         (Reading the data also resets the CHG flags in the control.)&lt;br /&gt;
&lt;br /&gt;
  220 ! process the changed rows now present in the data arrays --&lt;br /&gt;
  300 PRINT FIELDS &amp;quot;row,col,GRID rows/cols,RANGE&amp;quot;: MAT Rowsubs,&lt;br /&gt;
                   MAT Rowsubs, ( MAT Data1$, MAT Data2, MAT Data3$ )&lt;br /&gt;
&lt;br /&gt;
This outputs the updated rows. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Grid and List BR_VB Similarities  ====&lt;br /&gt;
Before introducing grids and lists in BR, similar effects could be achieved using BR_VB to work with Visual Basic. If you were familiar with BR_VB, the following notes may be of interest:&lt;br /&gt;
&lt;br /&gt;
Grid and list controls work like the BR_VB interface except headers now specify the form of each column and a new input type has been added: &lt;br /&gt;
&lt;br /&gt;
A multi column LISTview- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS[,hdrs][,fkey]&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FORMS$) &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note-&#039;&#039;&#039; Widths are expressed in character positions, not percentages like they are in BR_VB. &lt;br /&gt;
&lt;br /&gt;
The populate operation- &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
Read-Ctl type: CNT (in place of CELLROWSUB) returns a single numeric value which is the number of subscripts available to read. In the case of grids, this is the number of cells. In the case of LISTviews, this is the number of rows. &lt;br /&gt;
&lt;br /&gt;
 00110 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL&amp;quot;: avail_rows&amp;amp;nbsp;! # of selected rows&lt;br /&gt;
 00120 MAT DATA$(3 * avail_rows)         &amp;amp;nbsp;! redimension.. 3 cols x selected rows&lt;br /&gt;
 00130 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROW,SEL,NOWAIT&amp;quot;: MAT DATA$&amp;amp;nbsp;! read rows&lt;br /&gt;
&lt;br /&gt;
====See Also: ====&lt;br /&gt;
* [[Grids Tutorial]]&lt;br /&gt;
* [[0886]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Widget]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11441</id>
		<title>Grid and List</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11441"/>
		<updated>2024-02-05T05:15:42Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* FKey Processing */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This discussion assumes the reader has a clear understanding of [[INPUT FIELDS]] and [[PRINT FIELDS]] processing with simple fields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grid&#039;&#039;&#039; and &#039;&#039;&#039;List&#039;&#039;&#039; (a.k.a. &#039;&#039;&#039;ListView&#039;&#039;&#039;) are [[Two dimensional controls]], which means that they contain rows and columns. GRIDs are normally used for data entry, while LISTs are used only for selection. In Business Rules, outputting to these controls is done with [[Print Fields]] and inputting from them is done with [[Input Fields]]. Each field specification pertains to one [[Control]]. However, a single FIELD specification can pertain to a parenthesized group of arrays. When using Grids and Lists, first the header must be populated, and then the data within the rows. &lt;br /&gt;
&lt;br /&gt;
The [[INPUT FIELDS]] specification states Grid or List as the field type. The display area is specified in terms of rows and columns separated by a slash. The following parameter, instead of being leading field attributes, is a secondary keyword indicating the type (CNT/SUB/CELL/ROWCNT/ROWSUB/ROW) of output or input operation to be performed. The next parameter further qualifies the IO operation (CHG/SEL/ALL/CUR/NEXT). Normally this trailing attribute is an [[FKEY]] value which is shifted right one parameter in this context. &lt;br /&gt;
&lt;br /&gt;
It is useful to work with [[2D Controls]] in terms of rows versus cells, particularly when the columns are dissimilar. A complete set of row oriented parameters are provided for that purpose. Output operations support the mass populating of 2D controls row by row. And input operations can be row oriented as well. The keywords associated with row processing are R, ROWCNT, ROWSUB, and ROW. &lt;br /&gt;
&lt;br /&gt;
RINPUT does not work with 2D controls because the output statements are somewhat different from the input statements. The output consists of setting up the columns, including providing the column headings, and populating the control with data. The input consists of identifying what has changed in a manner that enables selective data retrieval and corresponding file updating. &lt;br /&gt;
&lt;br /&gt;
If the user clicks on a column heading the GRID or LIST will be sorted on that column. Sorting is done in terms of rows. Such sorting of these controls has no affect on the BR program. The information returned to BR will be as though no sorting were performed. If a control&#039;s population is increased by populating with the plus (+) flag, the control will be resequenced back to its original order before the data is added. One way a program can restore the original displayed order of a GRID or LIST is to populate it incrementally (+) with no data. As of version 4.1 and higher, if a GRID input is attempted on a protected field, BR issues a Bell. &lt;br /&gt;
&lt;br /&gt;
Shift+PgUp and Shift+PgDn selects within a List/Grid. &lt;br /&gt;
&lt;br /&gt;
Grids and lists are compatible with the [[FILTER]] field, which works like a search bar. &lt;br /&gt;
&lt;br /&gt;
In GRIDs and LISTs only, string arrays may be used to process numeric values. BR automatically performs [[VAL]] and [[STR]] conversions as needed.  If string data is passed to a numeric field type such as N or DATE then it is automatically converted to numeric form for internal processing (4.2).&lt;br /&gt;
&lt;br /&gt;
As of Business Rules! versions [[4.3]]+, arrays are automatically resized when receiving data from 2D INPUT operations. This also applies to grouped arrays. Automatic resizing only applies to one dimensional arrays and does not occur when INPUTing into two dimensional arrays. For example, where all arrays are one dimensional and may have the incorrect number of elements:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, ALL&amp;quot;&amp;amp;nbsp;: ( MAT Array1$,&amp;lt;span style=&amp;quot;font-family: monospace;&amp;quot;&amp;gt; &amp;lt;/span&amp;gt;MAT Array2$, MAT Array3, MAT Array4, MAT Array5$ )  &lt;br /&gt;
&lt;br /&gt;
====FKey Processing====&lt;br /&gt;
&lt;br /&gt;
An [[FKey]] value can be associated with a LIST or GRID control by specifying the FKey number during either output or input operations. Once an Fkey value is specified, the control retains the setting until it is reset. An FKey value can be cleared by specifying an Fkey value of minus one (-1). &lt;br /&gt;
&lt;br /&gt;
When a LIST or GRID has an FKey value set processing is dependent on whether or not the 2D control is being processed by an INPUT FIELDS statement: &lt;br /&gt;
&lt;br /&gt;
*Displayed but inactive- Single clicking any cell produces the FKey interrupt. &lt;br /&gt;
*Active (participating in Input Fields)- Double clicking any cell produces an FKey completion.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Formerly, [[CURROW]] and [[CURCOL]] represented the character position where the cursor was when FIELDS processing is completed. This is still true except when the most recent field processed is a [[2D control]]. &lt;br /&gt;
&lt;br /&gt;
When FIELDS processing ends and control is returned to the program while the &#039;current&#039; control is of type LIST or GRID, CURROW and CURCOL are set to the current cell row and column within the 2D control instead of the character position relative to the window.&lt;br /&gt;
&lt;br /&gt;
====GRID CURSOR MOVEMENT====&lt;br /&gt;
When field + or - is keyed BR always returns fkey values of 114 or 115 in both navigation and edit mode and for any type of data. &lt;br /&gt;
(This assumes the X attribute or some other attribute returns control to the program.)&lt;br /&gt;
&lt;br /&gt;
Default cursor positioning for Field +/- keys is to perform a down arrow operation, and the Enter key defaults to NONE (no movement). &lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- forces the signing of a numeric field, whether or not the field type is PIC.&lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- also right truncates any character or numeric data before exiting the field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A Config statement can be used to override (control) grid cursor movement. When this is used, both Field +/- and Enter produce the same cursor movement:&lt;br /&gt;
&lt;br /&gt;
 GRID_CURSOR_MOVE  DOWN | RIGHT | NONE | DEFAULT&lt;br /&gt;
&lt;br /&gt;
This determines the field cursor position after keying Enter or Field +/-. Both navigation and edit mode produce the same resulting cursor position.&lt;br /&gt;
&lt;br /&gt;
====Restoring a User Sorted 2D Control====&lt;br /&gt;
&lt;br /&gt;
In versions 4.3 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns. &lt;br /&gt;
&lt;br /&gt;
==== A multi column LISTview  ====&lt;br /&gt;
&lt;br /&gt;
 01000 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS,[HDRS][,fkey]&amp;quot;: (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Widths are expressed in character positions.&lt;br /&gt;
&lt;br /&gt;
The [HDRS] notation refers to an optional CONFIG ATTRIBUTE HDRS specification for setting the appearance of the header row. In this case the  brackets [] are required and the term HDRS may be any bracketed attribute name. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT HEADINGS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the titles that will appear at the top of each column in the List or Grid. The format of this row may be specified by an optional parameter following HEADERS in the above example.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT WIDTHS&#039;&#039;&#039; &lt;br /&gt;
| Specifies the number of characters in each column. For example if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT FORMS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the display characteristics of each column such as &amp;quot;C 12&amp;quot; or &amp;quot;PIC(z,zzz,zz#.##-)&amp;quot;. A &amp;quot;P&amp;quot; following the display parameter will cause the field to be protected and no data entry will be allowed in GRID mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; The field form array elements may also include a trailing comma followed by field attributes (e.g. color) that pertain to the column. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80, = [,fkey]&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt.&lt;br /&gt;
&lt;br /&gt;
==== Reading a Listview or Grid  ====&lt;br /&gt;
When using INPUT FIELDS to read from a 2D control, the leading attributes specification states the type of read operation and the trailing attributes specification is the type of cell or row selection to be performed. The third parameter can optionally specify NOWAIT or an FKEY value. If it is an FKEY value, it signifies that an FKEY event should be triggered when, in navigation mode, a selection is made by double clicking or pressing the Enter key.&lt;br /&gt;
 &lt;br /&gt;
===== Syntax  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Grid.png|950px]]&lt;br /&gt;
&lt;br /&gt;
=====Parameters=====&lt;br /&gt;
Quotation marks must surround the specifications, and individual parts must be separated by commas.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Row&#039;&#039;&#039; and &#039;&#039;&#039;Column&#039;&#039;&#039; specify the space where the grid or list begins.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;List&#039;&#039;&#039; or &#039;&#039;&#039;Grid&#039;&#039;&#039;, followed by rows and columns separated by a slash determine how big the list or grid is going to be. The main difference between a list and grid is that lists are for selection only while information can be added to grids directly. &lt;br /&gt;
&lt;br /&gt;
The following &#039;&#039;&#039;Read Types&#039;&#039;&#039; are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowCnt&#039;&#039;&#039; &lt;br /&gt;
| The number of rows specified.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowSub&#039;&#039;&#039; &lt;br /&gt;
| The subscripts of the specified rows.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Row&#039;&#039;&#039; &lt;br /&gt;
| Read all cells in each specified row.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Colcnt&#039;&#039;&#039; &lt;br /&gt;
|The number of columns established by the header arrays. e.g. INPUT FIELDS &amp;quot;row,col,LIST rows/cols, COLCNT, ALL&amp;quot; : numeric-variable&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sort_Order&#039;&#039;&#039; &lt;br /&gt;
|(4.3+) Provides a value of zero for each unsorted column and gives the ascending sequence of column sorts that have occurred. If a column has been reversed (double sorted) it&#039;s value will be negative. The selection typed used must be ALL. For example: INPUT FIELDS &amp;quot;row,col,GRID rows/cols, SORT_ORDER, ALL&amp;quot; : Mat NumArray, with the following history of sorting a four column GRID:&lt;br /&gt;
 column 1 (descending most recent)&lt;br /&gt;
 column 2 (ascending first sorted)&lt;br /&gt;
 column 3 (not sorted)&lt;br /&gt;
 column 4 (sorted ascending)&lt;br /&gt;
&lt;br /&gt;
SORT_ORDER would return-&lt;br /&gt;
 array(1) -&amp;gt;  -1&lt;br /&gt;
 array(2) -&amp;gt;   3&lt;br /&gt;
 array(3) -&amp;gt;   0&lt;br /&gt;
 array(4) -&amp;gt;   2&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;HEADERS&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) The read operation returns the original PRINT FIELDS HEADER values. For example:  INPUT FIELDS &amp;quot;row,col,LIST rows/cols, HEADERS,ALL,NOWAIT&amp;quot; : (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$) The selection type used must be ALL.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MASK&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) MASK can be used with Grids and Lists. As a READ type, this reads the display mask setting, including listviews that have been displayed according to a [[filter]] or filterbox. For example: INPUT FIELDS &amp;quot;row,col,LIST rows/cols,MASK [,NOWAIT]&amp;quot; : mask_array. The mask array affects only the user presentation and not the data. Use RANGE processing or the CHG selection type to selectively read from a 2D control.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These Read Types are valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cnt&#039;&#039;&#039; &lt;br /&gt;
| Specify the number of cells specified (see selection types below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sub&#039;&#039;&#039; &lt;br /&gt;
| Read the Cell Subscript Values (see example below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell&#039;&#039;&#039; &lt;br /&gt;
| Read each cell specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For the &amp;quot;Sel-type&amp;quot; parameter, the following selection types are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sel&#039;&#039;&#039; &lt;br /&gt;
| Read one or more selected items.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;SelOne&#039;&#039;&#039; &lt;br /&gt;
| Select only one item.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;All&#039;&#039;&#039; &lt;br /&gt;
| Read all items in the control (except headings).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cur&#039;&#039;&#039; &lt;br /&gt;
| Current cell or row number.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Next (4.2+)&#039;&#039;&#039; &lt;br /&gt;
| The cell the cursor is going to next if the user moved it using an arrow or a mouse click.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Range&#039;&#039;&#039;&lt;br /&gt;
|Specifies which portion of a 2D control is to be input. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell_Range&#039;&#039;&#039;&lt;br /&gt;
|A special type of output range. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This Selection Type is valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Chg&#039;&#039;&#039; &lt;br /&gt;
| All items changed since the last &#039;=&#039; populate or the last CHG retrieval of cell/row contents.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039; is an optional parameter to help the read. As of 4.3 it can be [[DISPLAYED_ORDER]]. #[[PIC]] or #[[FMT]] could be used. #PIC and #FMT allow numeric data from a string to be used. For example: &#039;&#039;&#039;DISPLAYED_ORDER&#039;&#039;&#039; Indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program (as of version 4.30). This reads the original row subscripts for all rows - in their present order - and only works with the ALL selection type.&lt;br /&gt;
&lt;br /&gt;
GRIDLINES makes LIST controls look like GRIDs with respect to the display of data (column and row separators).&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80,GRIDLINES&amp;quot;: 1 | 0 (on or off)&lt;br /&gt;
&lt;br /&gt;
The leading attribute values &amp;quot;^select&amp;quot; or &amp;quot;^deselect&amp;quot; may be specified to allow the pre-selection of GRID / LIST Elements: &lt;br /&gt;
 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,^select ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Fkey&#039;&#039;&#039; and &#039;&#039;&#039;Nowait&#039;&#039;&#039; parameters are optional. FKEY means that an FKEY event should be triggered when a selection is made by double clicking or pressing the Enter key, in navigation mode. Nowait simply means that it does not wait for user input.&lt;br /&gt;
&lt;br /&gt;
Following the ending quotation mark, a colon precedes the name of the I/O List.&lt;br /&gt;
&lt;br /&gt;
====DISPLAYED ORDER &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039;==== &lt;br /&gt;
&lt;br /&gt;
As of 4.30, [[DISPLAYED ORDER]] - indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program, for example:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROWSUB, ALL, DISPLAYED_ORDER, NOWAIT&amp;quot;: numeric-array &lt;br /&gt;
&lt;br /&gt;
This reads the original row subscripts for all rows in their present order. This qualifier works only with the ALL selection type. It may be used in conjunction with other qualifiers such as FKEY.&lt;br /&gt;
&lt;br /&gt;
==== Examples  ====&lt;br /&gt;
&lt;br /&gt;
===== LIST  =====&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL,FKEY&amp;quot;: avail_rows&amp;amp;nbsp;! selected row cnt&lt;br /&gt;
 00220&amp;amp;nbsp;! next INPUT operation does not wait for operator&lt;br /&gt;
 00230 MAT subscr(avail_rows)         &amp;amp;nbsp;! redimension with number of selected rows&lt;br /&gt;
 00240 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWSUB,SEL,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
&lt;br /&gt;
===== Uniform GRID  =====&lt;br /&gt;
Contains one data array and multiple columns&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CNT,CHG&amp;quot;: cells     &amp;amp;nbsp;! # of changed cells&lt;br /&gt;
 00220 MAT subscr(cells)                                        &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,SUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT data$(cells)                                         &amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CELL,CHG,NOWAIT&amp;quot;: MAT data$&amp;amp;nbsp;! read changes&lt;br /&gt;
&lt;br /&gt;
===== Row Oriented GRID  =====&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWCNT,CHG&amp;quot;: rows  &amp;amp;nbsp;! # of changed rows&lt;br /&gt;
 00220 MAT subscr(rows)                                  &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWSUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT NAME$(rows)&amp;amp;nbsp;: MAT CITY$(rows)&amp;amp;nbsp;: MAT AGE(rows)&amp;amp;nbsp;: MAT WEIGHT(rows)&amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROW,CHG,NOWAIT&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)  &amp;amp;nbsp;! read changed rows&lt;br /&gt;
&lt;br /&gt;
This brings us to the question of what is to be done with the information after it has been read. If it is to be stored in a file, then we should have included a hidden column with master file record numbers of the data in each row. This would support looping through the input array and rewriting the changed data. &lt;br /&gt;
&lt;br /&gt;
While LIST subscripts are expressed in terms of rows, GRID subscripts may be either. In a four column five row GRID, the cell at row three column two has a subscript of ten.&lt;br /&gt;
&lt;br /&gt;
==== Cell Subscript Values of a 5 x 4 GRID  ====&lt;br /&gt;
&lt;br /&gt;
 1   2   3   4&lt;br /&gt;
 5   6   7   8&lt;br /&gt;
 9  10  11  12&lt;br /&gt;
 13 14  15  16&lt;br /&gt;
 17 18  19  20&lt;br /&gt;
&lt;br /&gt;
==== Sample Program====&lt;br /&gt;
The following example shows use of LIST and GRID controls. There are seven parts in this program. &lt;br /&gt;
&lt;br /&gt;
The first part creates a LIST with 2 columns and 3 rows. On lines 300 - 500, column headings, widths, and form specifications are assigned to the corresponding matrices. Lines 600 and 700 place data into the matrices to be printed in the LIST. The HEADERS operation on line 800 sets the column headings, widths and form specs. Line 900 populates the LIST by row, which is the default. &lt;br /&gt;
&lt;br /&gt;
 00100 dim HEADINGS$(2), WIDTHS(2), FORMS$(2), NAMES$(3)*28, CITIES$(3)*18, DATA$(1)*80, SUBSCR(1)&lt;br /&gt;
 00200 print NEWPAGE&lt;br /&gt;
 00300 let HEADINGS$(1)=&amp;quot;Name&amp;quot;: let HEADINGS$(2)=&amp;quot;City&amp;quot;&lt;br /&gt;
 00400 let WIDTHS(1)=30&amp;amp;nbsp;: let WIDTHS(2)=20&lt;br /&gt;
 00500 let FORMS$(1)=&amp;quot;CC 28&amp;quot;&amp;amp;nbsp;: let FORMS$(2)=&amp;quot;CC 18&amp;quot;&lt;br /&gt;
 00600 let NAMES$(1)=&amp;quot;Stalin&amp;quot;&amp;amp;nbsp;: let NAMES$(2)=&amp;quot;Napoleon&amp;quot;&amp;amp;nbsp;: let NAMES$(3)=&amp;quot;Roosevelt&amp;quot;&lt;br /&gt;
 00700 let CITIES$(1)=&amp;quot;Moscow&amp;quot;&amp;amp;nbsp;: let CITIES$(2)=&amp;quot;Paris&amp;quot;&amp;amp;nbsp;: let CITIES$(3)=&amp;quot;Washington&amp;quot;&lt;br /&gt;
 00800 print fields &amp;quot;1,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 00900 print fields &amp;quot;1,1,list 8/60,=R&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
 01000 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at the end of list&amp;quot;&lt;br /&gt;
 01100 let KSTAT$(1)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output1.jpg]] &lt;br /&gt;
&lt;br /&gt;
The second part of the program demonstrates the use of the primary flag +, which adds to the end of any previously populated data. Line 01200 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 01300 places new data into the matrices to be printed in the LIST.&amp;lt;br&amp;gt; Line 01400 adds the new data to the LIST. &lt;br /&gt;
&lt;br /&gt;
 01200 mat NAMES$(UDIM(NAMES$)+1)&amp;amp;nbsp;: mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 01300 let NAMES$(4)=&amp;quot;Churchill&amp;quot;&amp;amp;nbsp;: let CITIES$(4)=&amp;quot;London&amp;quot;&lt;br /&gt;
 01400 print fields &amp;quot;1,1,list 8/60,+&amp;quot;: (MAT NAMES$(4:4),MAT CITIES$(4:4))&lt;br /&gt;
 01500 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Select rows to be read into a matrix&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output2.jpg]] &lt;br /&gt;
&lt;br /&gt;
The third part of the program demonstrates use of read types ROWSUB and ROWCNT and selection type SEL. Line 1600 inputs the number of selected rows into AVAIL_ROWS. The user may select rows using the mouse or the keyboard and SHIFT and CTRL keys. Line 1700 redimensions matrix SUBSCR to the number of selected rows. Line 1800 inputs the subscripts of the selected rows into matrix SUBSCR. Line 1900 performs a HEADERS operation for a new LIST using the same matrices as were used in the previous LIST. Line 2000 populates the first row of the new LIST with the data from the first selected row from the previous LIST using the primary flag =. If user selects more than one row, then lines 2100 - 2500 add the data from the selected rows using the primary flag +. &lt;br /&gt;
&lt;br /&gt;
 01600 input fields &amp;quot;1,1,list 8/60,ROWCNT,SEL&amp;quot;: AVAIL_ROWS&lt;br /&gt;
 01700 mat SUBSCR(AVAIL_ROWS)&lt;br /&gt;
 01800 input fields &amp;quot;1,1,list 8/60,rowsub,sel,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 01900 print fields &amp;quot;12,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 02000 print fields &amp;quot;12,1,list 8/60,=&amp;quot;: (MAT NAMES$(SUBSCR(1):SUBSCR(1)),MAT CITIES$(SUBSCR(1):SUBSCR(1)))&lt;br /&gt;
 02100 if UDIM(SUBSCR) &amp;amp;gt; 1 then&lt;br /&gt;
 02200   for I = 2 to UDIM(SUBSCR)&lt;br /&gt;
 02300     print fields &amp;quot;12,1,list 8/60,+&amp;quot;: ( MAT NAMES$(SUBSCR(I):SUBSCR(I)),MAT CITIES$(SUBSCR(I):SUBSCR(I)))&lt;br /&gt;
 02400   next I&lt;br /&gt;
 02500 end if&lt;br /&gt;
 02600 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at beginning of list&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output3.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fourth part of the program demonstrates the use of the primary flag -, which inserts in the beginning of any previously populated data. Line 2800 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 2900 places new data into the matrices to be printed in the LIST. Line 3000 adds the new data to the beginning of the LIST ahead of previously populated data. &lt;br /&gt;
&lt;br /&gt;
 02700 let KSTAT$(1)&lt;br /&gt;
 02800 mat NAMES$(UDIM(NAMES$)+1): mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 02900 let NAMES$(5)=&amp;quot;Castro&amp;quot;&amp;amp;nbsp;:! let CITIES$(5)=&amp;quot;Havana&amp;quot;&lt;br /&gt;
 03000 print fields &amp;quot;1,1,list 8/60,-&amp;quot;: (MAT NAMES$(5:5),MAT CITIES$(5:5))&lt;br /&gt;
 03100 print fields &amp;quot;9,1,C 60&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to populate list by column&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output4.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fifth part of the program, more specifically, Line 3300, demonstrates the use of the secondary flag C to populate the LIST by column. The primary flag = is also used in order to replace any previously populated data. &lt;br /&gt;
&lt;br /&gt;
 03200 let KSTAT$(1)&lt;br /&gt;
 03300 print fields &amp;quot;1,1,list 8/60,=C&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output5.jpg]] &lt;br /&gt;
&lt;br /&gt;
The sixth part of the program creates a GRID. The HEADERS operation on line 3600 uses the same HEADINGS$, WIDTHS, and FORMS$ as the previously constructed LISTs. Line 3700 sets CURFLD to be on the sixth cell of the GRID (this is discussed in the next section). Line 3800 populates the GRID. The user may change the contents of the cells. &lt;br /&gt;
&lt;br /&gt;
 03400 print fields &amp;quot;23,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to continue&amp;quot;: let KSTAT$(1)&lt;br /&gt;
 03500 print NEWPAGE&lt;br /&gt;
 03600 print fields &amp;quot;1,1,grid 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 03700 let CURFLD (1,6)&lt;br /&gt;
 03800 print fields &amp;quot;1,1,grid 8/60,=&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6.jpg]] &lt;br /&gt;
&lt;br /&gt;
The seventh part of the program demonstrates the use of read types CNT, CELL, and SUB and selection type CHG. Line 3900 counts the number of changed cells and inputs that number into variable CELLS. Line 4000 redimensions the matrix SUBSCR to the number of changed cells. Line 4100 inputs the changed cells into SUBSCR. Line 4200 redimensions the matrix DATA$.  Line 4300 inputs the subscripts of the changed cells into matrix DATA$. Line 4400 prints DATA$. &lt;br /&gt;
&lt;br /&gt;
 03900 input fields &amp;quot;1,1,grid 8/60,cnt,chg&amp;quot;: CELLS&lt;br /&gt;
 04000 mat SUBSCR(CELLS)&lt;br /&gt;
 04100 input fields &amp;quot;1,1,grid 8/60,sub,chg,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 04200 mat DATA$(CELLS)&lt;br /&gt;
 04300 input fields &amp;quot;1,1,grid 8/60,cell,chg,nowait&amp;quot;: MAT DATA$&lt;br /&gt;
 04400 print MAT DATA$&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6b.jpg]]&lt;br /&gt;
&lt;br /&gt;
===== Output of line 04400  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output7.jpg]]&lt;br /&gt;
&lt;br /&gt;
=== Displaying a List or Grid (Output Operations) ===&lt;br /&gt;
[[file:Grid2.png|900px]]&lt;br /&gt;
&lt;br /&gt;
To display a listview or a grid, you must set the headers first, using a special PRINT FIELDS operation.&lt;br /&gt;
&lt;br /&gt;
====HEADERS====&lt;br /&gt;
The HEADERS operation sets the column headings and widths. The corresponding input/output list value must be a parenthesized group of three arrays, for example: &lt;br /&gt;
&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$)&lt;br /&gt;
      - or -&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS,MAT FIELD_FORMS$)&lt;br /&gt;
&lt;br /&gt;
The [hdrs] notation refers to an optional CONFIG ATTRIBUTE [HDRS] specification for setting the appearance of the header row. In this case the [] brackets are required. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
MAT HEADINGS$ Contains the column titles that will be displayed at the top of each column. The font, color and shading of these titles can be set through the [HDRS] or similar substitution attribute. &lt;br /&gt;
&lt;br /&gt;
MAT WIDTHS specifies DISPLAYED Column Widths and is expressed as the number of character positions occupied by each column. Scrollbars are provided as needed to honor overall control size specified in the FIELDS specification. &lt;br /&gt;
&lt;br /&gt;
For example, if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area. &lt;br /&gt;
&lt;br /&gt;
Displayed widths of zero characters are allowed. This enables the use of hidden columns for storing things like record numbers and record keys. &lt;br /&gt;
&lt;br /&gt;
MAT FIELD_FORMS$ provides the BR FORM for each column. e.g. C 15 stipulates a maximum field capacity of 15. The actual displayed length is a function of the grid size and the column relative width. &lt;br /&gt;
&lt;br /&gt;
The field form array elements may also include a comma followed by leading field attributes (e.g. color) pertaining to the column.  &lt;br /&gt;
&lt;br /&gt;
The number of columns must be set with HEADERS prior to Populating a control (loading data into it).&lt;br /&gt;
&lt;br /&gt;
====MASKing====&lt;br /&gt;
&lt;br /&gt;
As of 4.3, LIST and GRID support the MASK operation, both in READs and Populating, as seen in this section. For example:&lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;row,col,LIST rows/cols,MASK&amp;quot; :  mask_array&lt;br /&gt;
&lt;br /&gt;
This restricts the rows (for both LIST and GRID) previously displayed to those corresponding to a “true” value in mask_array. A true value is represented in a numeric array as a value greater than zero. Negative values are not allowed in mask arrays. A string mask array may also be used with “T” and “F” values. The MASK stays in effect until 1) a new MASK is specified or 2) the contents of the control are changed with PRINT ( &amp;lt;nowiki&amp;gt;=, +, -,&amp;lt;/nowiki&amp;gt; see primary flags below).  Also, the mask array affects only the user presentation, not the result set. &lt;br /&gt;
&lt;br /&gt;
====Populating====&lt;br /&gt;
&lt;br /&gt;
The populate operation loads data into the control. In the following example four columns are loaded: &lt;br /&gt;
&lt;br /&gt;
 03010 PRINT FIELDS &amp;quot;10,20,LIST 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)&lt;br /&gt;
      - or -&lt;br /&gt;
 03020 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt. &lt;br /&gt;
&lt;br /&gt;
Permissible leading attribute values are:&lt;br /&gt;
&lt;br /&gt;
===== Primary Flags  =====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;=&#039;&#039;&#039; &lt;br /&gt;
| Replace any previous data&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;+&#039;&#039;&#039; &lt;br /&gt;
| Add to any previously populated data (this allows loading in chunks)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;-&#039;&#039;&#039; &lt;br /&gt;
| Insert data ahead of previously populated data (4.16+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Secondary Flags  ====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;R&#039;&#039;&#039; &lt;br /&gt;
| Load one row at a time (the default - use grouped IO parens)&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;C&#039;&#039;&#039; &lt;br /&gt;
| Load one column at a time - This is for loading multiple columns of the same data type&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;L&#039;&#039;&#039; &lt;br /&gt;
| Provide the FKEY (see INPUT below) or Enter interrupt if the user presses up arrow or page up in the first field, or down arrow or page down in the last field. Note that this is not specified in the individual field leading attributes.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;S&#039;&#039;&#039; &lt;br /&gt;
| Single click to activate an Enter or FKEY event (otherwise a double click is required) (4.17+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Note that the following example will NOT work- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=C&amp;quot;: MAT NAME$,MAT CITY$,MAT AGE,MAT WEIGHT&lt;br /&gt;
&lt;br /&gt;
The reason is that only MAT NAME$ will reach the list. MAT CITY$, MAT AGE and MAT WEIGHT will be associated with subsequent fields. Multiple arrays provided to a single control must be grouped with parentheses. &lt;br /&gt;
&lt;br /&gt;
Populating a two-dimensional object by row with grouped IO means NAME$(1) will go into (1,1), AGE(1) will go into (1,2) and WGT(1) will go into (1,3) and so on. If a single array (MAT DATA$) is specified instead of a group, MAT DATA$ is applied horizontally instead of vertically. So DATA$(1) - DATA$(3) will be the first row and DATA$(4) - DATA$(6) will be the next row and so on. &lt;br /&gt;
&lt;br /&gt;
Populating a two dimensional grid by column with an array named DATA$ means that DATA$(1) goes in (1,1) and DATA$(2) goes in (2,1) and DATA$(3) goes in (3,1). Therefore the first however many values of DATA refer to the first column and the second however many values of DATA refer to the second column. So if there are 3 columns and UDIM(DATA$) = 90 then DATA$(1)-DATA$(30) is the first column, and DATA$(31) - DATA$(60) is the second column and DATA$(61) - DATA$(90) is the last column. &lt;br /&gt;
&lt;br /&gt;
For example, using the following information, each example demonstrates how the grid will be filled:&lt;br /&gt;
&lt;br /&gt;
MAT NAME$ = George, Peter, Tom &lt;br /&gt;
&lt;br /&gt;
MAT CITY$ = Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
MAT AGE$ = 42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
MAT WEIGHT$ = 180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Peter, Detroit, 23, 212 &lt;br /&gt;
&lt;br /&gt;
Tom, Denver, 35, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =C&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
As you can see using C with grouped arrays is counter-intuitive and doesn&#039;t fit well. C is most useful with a single array that should be loaded vertically down several columns. &lt;br /&gt;
&lt;br /&gt;
===== Grid Validation  =====&lt;br /&gt;
&lt;br /&gt;
GRIDs are now validated as each cell is exited instead of when control is passed to the BR program after all data is entered.&lt;br /&gt;
&lt;br /&gt;
===== Color and Font changes in Cells  =====&lt;br /&gt;
&lt;br /&gt;
The attributes that determine font, color and style in each cell can be set for an entire column by including these parameters in the heading FORM array. Individual cells can then be changed using a PRINT statement. &lt;br /&gt;
&lt;br /&gt;
The format of the print statement is &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT #WINNO, fields &amp;quot;2,2,LIST 10/60,ATTR&amp;quot;:(mat start, mat end, mat attribute$)&lt;br /&gt;
&lt;br /&gt;
With the following parameter descriptions: &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat start&#039;&#039;&#039; &lt;br /&gt;
| contains the cell number(s) where the attribute chain begins,&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat end&#039;&#039;&#039; &lt;br /&gt;
| contains the last cell number where the attribute applies&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat attribute$&#039;&#039;&#039; &lt;br /&gt;
| contains the attribute specification that should be applied to the cell range(s) specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If the 2d control is a GRID, then mat Start and mat End refer to starting and ending Cell Numbers. If the 2d control is a listview, then mat Start and mat End refer to starting and ending Row Numbers. &lt;br /&gt;
&lt;br /&gt;
The attributes specified for any COLUMN can be overridden on a cell basis by specifying the starting cell number, ending cell number, and the overriding attributes in three arrays that are printed to the grid window with the same grid specificatoins and the key word &amp;quot;ATTR&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
 02420 PRINT #BLISTWIN,FIELDS BLISTSPEC$&amp;amp;amp;&amp;quot;,ATTR&amp;quot;: (MAT BROWS,MAT BROWE,MAT BATT$)&lt;br /&gt;
&lt;br /&gt;
In the above example, BLISTWIN is the window number, BLISTSPEC$ is the grid specification (&amp;quot;GRID 10/40&amp;quot; for example), BROWS is an array holding the starting cell number, BROWE is an array holding the ending cell number, and BATT$ is an array holding the overriding attributes. In a list the attributes of the first cell in the row controls the appearance of the entire row.&lt;br /&gt;
&lt;br /&gt;
 01500 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The above example overrides the attributes of a range of cells/rows for a GRID/LIST display. This allows you to shade or otherwise alter the display of a range of cells / rows in a 2D control.&lt;br /&gt;
&lt;br /&gt;
====Aggregate Sorting====&lt;br /&gt;
BR supports aggregated sorting for LISTs and GRIDs. This means when clicking &lt;br /&gt;
on various column headings or programmatically sorting columns, fields of &lt;br /&gt;
equal values retain their previous order within their new groupings (4.2).&lt;br /&gt;
&lt;br /&gt;
==== Numeric Column Sorting  ====&lt;br /&gt;
&lt;br /&gt;
2D controls now facilitate numeric column sorting. This works well in conjunction with the new [[Date (Format Specification)|DATE]] field format (see release notes [[4.16]]) where the data is stored as day of century, but is displayed as a formatted date. It also works with all numeric columns.&lt;br /&gt;
&lt;br /&gt;
If a listview columns form spec if given as DATE(mm/dd/ccyy), for example, then any time that column is sorted, either as a result of the user clicking on the column heading, or the program giving the sort command (shown below), the dates are properly sorted even though they&#039;re displayed as mm/dd/ccyy. For this to work, the data has to be given in the julian days format, see the [[DAYS]] function for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In versions 4.2 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
To sort in reverse order, sort the column twice:&lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { same column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended in version 4.3 to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns.&lt;br /&gt;
&lt;br /&gt;
==== NOSORT for Columns  ====&lt;br /&gt;
As of 4.2, the &#039;&#039;&#039;NoSort&#039;&#039;&#039; parameter is used to prevent users from sorting columns of a Grid or List. &lt;br /&gt;
&lt;br /&gt;
For the statement: &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$) &lt;br /&gt;
&lt;br /&gt;
The field attribute &amp;quot;^nosort&amp;quot; appearing in the MAT FIELD_FORM$ prevents the sorting of a grid or listview in response to the user clicking on the corresponding column header. This does not prevent programs from sorting on those columns.&lt;br /&gt;
&lt;br /&gt;
==== Range Input  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are used in versions 4.3 and higher. In these examples BR will redimension the receiving arrays as needed: &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, CELL, RANGE&amp;quot; :&lt;br /&gt;
             start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This reads the specified range of cells. BR redimensions MAT Data$ as needed. Note that CELL may now be used with LIST. Previously, LISTs were only addressable by row. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot; :&lt;br /&gt;
             (start:=7), (end:=11), ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads the cells in rows 7 through 11. The receiving arrays are re-dimensioned as appropriate. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,GRID rows/cols, ROW, RANGE&amp;quot;:&lt;br /&gt;
             MAT start, MAT end, ( MAT Data1$, MAT Data2$, MAT Data3 )&lt;br /&gt;
&lt;br /&gt;
This reads one or more ranges of rows. &lt;br /&gt;
&lt;br /&gt;
A more detailed example of this is: &lt;br /&gt;
&lt;br /&gt;
 100 ! create and populate a LIST control &lt;br /&gt;
 200 MAT START(3) : MAT END(3)&lt;br /&gt;
 210 READ MAT START, MAT END&lt;br /&gt;
 220 DATA 7,21,33&lt;br /&gt;
 230 DATA 11,21,38&lt;br /&gt;
 240 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot;&amp;amp;nbsp;: MAT START,&lt;br /&gt;
            MAT END, ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads 12 rows of data ( row 7-11, row 21 and rows 33-38 ). &lt;br /&gt;
&lt;br /&gt;
==== Range Output  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions [[4.3]] and higher. By default, RANGE output refers to rows. The special keyword CELL_RANGE is used to denote the specification of cell ranges. Additionally, the use of scalars versus arrays for start and end values determines important characteristics of the output process. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Using Scalars For Range Specification &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,GRID 10/75, RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This replaces the values in rows numbered &#039;start&#039; through &#039;end&#039; with the data in MAT Data$. The size of MAT Data$ must be a multiple of the number of columns in the GRID or an error is generated. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$,                                   MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
This replaces the values in ROWs numbered &#039;start&#039; through &#039;end&#039; with the data from MATs NAME$, CITY$, AGE and WEIGHT. The data arrays must all be dimensioned the same. &lt;br /&gt;
&lt;br /&gt;
==== Insertion and Deletion with RANGE ====&lt;br /&gt;
&lt;br /&gt;
The number of rows being output do not need to match the number of rows being replaced. To delete a range of rows, output one or more grouped arrays with zero elements. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. Using the following statement, various scenarios are described. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays have been DIMed to nine elements&lt;br /&gt;
 Result- Nine rows replace five, and the total content of the control is expanded by 4 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays are DIMed to zero elements&lt;br /&gt;
 Result- Five rows are deleted, and the total size of the control is reduced by 5 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=0 (anything less than 7), and the arrays are DIMed to support three rows&lt;br /&gt;
 Result- Three rows are inserted ahead of row seven and the total content of the control is expanded by three rows&lt;br /&gt;
&lt;br /&gt;
 start=5000, end={any value}, the control only has 482 rows, and the source arrays are DIMed to support eleven rows&lt;br /&gt;
 Result- Eleven rows are appended to the end of the control and become rows 483 through 493.&lt;br /&gt;
&lt;br /&gt;
==== Outputting Ranges of Cells  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. &lt;br /&gt;
&lt;br /&gt;
Ranges of cells may be output in conjunction with the CELL_RANGE keyword. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, CELL_RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
                                - or -&lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
In this case, start and end specify cells instead of rows. If insertion or deletion is indicated by dimensioning the data arrays to greater or fewer elements than are being replaced, then the data must be a multiple of the number of columns. Insertion and deletion is only valid in terms of rows, even when cell subscripts are used to specify ranges. In such cases, if the cell subscripts are not on row boundaries, an error is generated. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, start, Data$&lt;br /&gt;
&lt;br /&gt;
In this example, the value in one cell is replaced with the content of a scalar.&lt;br /&gt;
&lt;br /&gt;
==== Using Arrays For Range Specification  ====&lt;br /&gt;
&lt;br /&gt;
If the start and end specifications are array denoting multiple ranges, there must be a one to one correspondence between the number of rows specified and those in the data. This method implies replacement only and insertion or deletion is not allowed. &lt;br /&gt;
&lt;br /&gt;
The data flow that this feature was designed to support is one where the user is presented with a LIST or GRID where multiple rows have been either selected or changed before returning control to the program and the program is responding by updating something on those rows. &lt;br /&gt;
&lt;br /&gt;
The program begins by presenting a 2D control to the user and reading the the control with type ROWSUB or SUB. Type SUB only works for GRIDs where all colmns have the same data type. Of course the subscripts are read into a numeric array which BR redimensions as appropriate. Then the program reads the changed or selected data with NOWAIT. (This resets the CHG flags in the control.) The program then changes either row (ROWSUB) or cell (SUB) data and outputs the results using the subscript array as both the start and end specification. Other scenarios are possible but this is the primary intended use. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher: &lt;br /&gt;
&lt;br /&gt;
  100 ! create and populate a GRID --&lt;br /&gt;
  200 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROWSUB,CHG&amp;quot;: MAT Rowsubs&lt;br /&gt;
         (Reading subscripts does not reset the CHG flags in the control.)&lt;br /&gt;
  210 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROW,CHG,NOWAIT&amp;quot;: ( MAT Data1$,&lt;br /&gt;
        MAT Data2, MAT Data3$ )&lt;br /&gt;
          BR redimensions the receiving arrays as needed.&lt;br /&gt;
         (Reading the data also resets the CHG flags in the control.)&lt;br /&gt;
&lt;br /&gt;
  220 ! process the changed rows now present in the data arrays --&lt;br /&gt;
  300 PRINT FIELDS &amp;quot;row,col,GRID rows/cols,RANGE&amp;quot;: MAT Rowsubs,&lt;br /&gt;
                   MAT Rowsubs, ( MAT Data1$, MAT Data2, MAT Data3$ )&lt;br /&gt;
&lt;br /&gt;
This outputs the updated rows. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Grid and List BR_VB Similarities  ====&lt;br /&gt;
Before introducing grids and lists in BR, similar effects could be achieved using BR_VB to work with Visual Basic. If you were familiar with BR_VB, the following notes may be of interest:&lt;br /&gt;
&lt;br /&gt;
Grid and list controls work like the BR_VB interface except headers now specify the form of each column and a new input type has been added: &lt;br /&gt;
&lt;br /&gt;
A multi column LISTview- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS[,hdrs][,fkey]&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FORMS$) &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note-&#039;&#039;&#039; Widths are expressed in character positions, not percentages like they are in BR_VB. &lt;br /&gt;
&lt;br /&gt;
The populate operation- &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
Read-Ctl type: CNT (in place of CELLROWSUB) returns a single numeric value which is the number of subscripts available to read. In the case of grids, this is the number of cells. In the case of LISTviews, this is the number of rows. &lt;br /&gt;
&lt;br /&gt;
 00110 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL&amp;quot;: avail_rows&amp;amp;nbsp;! # of selected rows&lt;br /&gt;
 00120 MAT DATA$(3 * avail_rows)         &amp;amp;nbsp;! redimension.. 3 cols x selected rows&lt;br /&gt;
 00130 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROW,SEL,NOWAIT&amp;quot;: MAT DATA$&amp;amp;nbsp;! read rows&lt;br /&gt;
&lt;br /&gt;
====See Also: ====&lt;br /&gt;
* [[Grids Tutorial]]&lt;br /&gt;
* [[0886]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Widget]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11440</id>
		<title>Grid and List</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11440"/>
		<updated>2024-02-05T05:14:33Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This discussion assumes the reader has a clear understanding of [[INPUT FIELDS]] and [[PRINT FIELDS]] processing with simple fields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grid&#039;&#039;&#039; and &#039;&#039;&#039;List&#039;&#039;&#039; (a.k.a. &#039;&#039;&#039;ListView&#039;&#039;&#039;) are [[Two dimensional controls]], which means that they contain rows and columns. GRIDs are normally used for data entry, while LISTs are used only for selection. In Business Rules, outputting to these controls is done with [[Print Fields]] and inputting from them is done with [[Input Fields]]. Each field specification pertains to one [[Control]]. However, a single FIELD specification can pertain to a parenthesized group of arrays. When using Grids and Lists, first the header must be populated, and then the data within the rows. &lt;br /&gt;
&lt;br /&gt;
The [[INPUT FIELDS]] specification states Grid or List as the field type. The display area is specified in terms of rows and columns separated by a slash. The following parameter, instead of being leading field attributes, is a secondary keyword indicating the type (CNT/SUB/CELL/ROWCNT/ROWSUB/ROW) of output or input operation to be performed. The next parameter further qualifies the IO operation (CHG/SEL/ALL/CUR/NEXT). Normally this trailing attribute is an [[FKEY]] value which is shifted right one parameter in this context. &lt;br /&gt;
&lt;br /&gt;
It is useful to work with [[2D Controls]] in terms of rows versus cells, particularly when the columns are dissimilar. A complete set of row oriented parameters are provided for that purpose. Output operations support the mass populating of 2D controls row by row. And input operations can be row oriented as well. The keywords associated with row processing are R, ROWCNT, ROWSUB, and ROW. &lt;br /&gt;
&lt;br /&gt;
RINPUT does not work with 2D controls because the output statements are somewhat different from the input statements. The output consists of setting up the columns, including providing the column headings, and populating the control with data. The input consists of identifying what has changed in a manner that enables selective data retrieval and corresponding file updating. &lt;br /&gt;
&lt;br /&gt;
If the user clicks on a column heading the GRID or LIST will be sorted on that column. Sorting is done in terms of rows. Such sorting of these controls has no affect on the BR program. The information returned to BR will be as though no sorting were performed. If a control&#039;s population is increased by populating with the plus (+) flag, the control will be resequenced back to its original order before the data is added. One way a program can restore the original displayed order of a GRID or LIST is to populate it incrementally (+) with no data. As of version 4.1 and higher, if a GRID input is attempted on a protected field, BR issues a Bell. &lt;br /&gt;
&lt;br /&gt;
Shift+PgUp and Shift+PgDn selects within a List/Grid. &lt;br /&gt;
&lt;br /&gt;
Grids and lists are compatible with the [[FILTER]] field, which works like a search bar. &lt;br /&gt;
&lt;br /&gt;
In GRIDs and LISTs only, string arrays may be used to process numeric values. BR automatically performs [[VAL]] and [[STR]] conversions as needed.  If string data is passed to a numeric field type such as N or DATE then it is automatically converted to numeric form for internal processing (4.2).&lt;br /&gt;
&lt;br /&gt;
As of Business Rules! versions [[4.3]]+, arrays are automatically resized when receiving data from 2D INPUT operations. This also applies to grouped arrays. Automatic resizing only applies to one dimensional arrays and does not occur when INPUTing into two dimensional arrays. For example, where all arrays are one dimensional and may have the incorrect number of elements:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, ALL&amp;quot;&amp;amp;nbsp;: ( MAT Array1$,&amp;lt;span style=&amp;quot;font-family: monospace;&amp;quot;&amp;gt; &amp;lt;/span&amp;gt;MAT Array2$, MAT Array3, MAT Array4, MAT Array5$ )  &lt;br /&gt;
&lt;br /&gt;
====FKey Processing====&lt;br /&gt;
&lt;br /&gt;
An [[FKey]] value can be associated with a LIST or GRID control by specifying the FKey number during either output or input operations. Once an Fkey value is specified, the control retains the setting until it is reset. An FKey value can be cleared by specifying an Fkey value of minus one (-1). &lt;br /&gt;
&lt;br /&gt;
When a LIST or GRID has an FKey value set processing is dependent on whether or not the 2D control is being processed by an INPUT FIELDS statement: &lt;br /&gt;
&lt;br /&gt;
*Displayed but inactive- Single clicking any cell produces the FKey interrupt. &lt;br /&gt;
*Active (participating in Input Fields)- Double clicking any cell produces an FKey completion.&lt;br /&gt;
&lt;br /&gt;
Formerly, [[CURROW]] and [[CURCOL]] represented the character position where the cursor was when FIELDS processing is completed. This is still true except when the most recent field processed is a [[2D control]]. &lt;br /&gt;
&lt;br /&gt;
When FIELDS processing ends and control is returned to the program while the &#039;current&#039; control is of type LIST or GRID, CURROW and CURCOL are set to the current cell row and column within the 2D control instead of the character position relative to the window.&lt;br /&gt;
&lt;br /&gt;
====GRID CURSOR MOVEMENT====&lt;br /&gt;
When field + or - is keyed BR always returns fkey values of 114 or 115 in both navigation and edit mode and for any type of data. &lt;br /&gt;
(This assumes the X attribute or some other attribute returns control to the program.)&lt;br /&gt;
&lt;br /&gt;
Default cursor positioning for Field +/- keys is to perform a down arrow operation, and the Enter key defaults to NONE (no movement). &lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- forces the signing of a numeric field, whether or not the field type is PIC.&lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- also right truncates any character or numeric data before exiting the field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A Config statement can be used to override (control) grid cursor movement. When this is used, both Field +/- and Enter produce the same cursor movement:&lt;br /&gt;
&lt;br /&gt;
 GRID_CURSOR_MOVE  DOWN | RIGHT | NONE | DEFAULT&lt;br /&gt;
&lt;br /&gt;
This determines the field cursor position after keying Enter or Field +/-. Both navigation and edit mode produce the same resulting cursor position.&lt;br /&gt;
&lt;br /&gt;
====Restoring a User Sorted 2D Control====&lt;br /&gt;
&lt;br /&gt;
In versions 4.3 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns. &lt;br /&gt;
&lt;br /&gt;
==== A multi column LISTview  ====&lt;br /&gt;
&lt;br /&gt;
 01000 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS,[HDRS][,fkey]&amp;quot;: (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Widths are expressed in character positions.&lt;br /&gt;
&lt;br /&gt;
The [HDRS] notation refers to an optional CONFIG ATTRIBUTE HDRS specification for setting the appearance of the header row. In this case the  brackets [] are required and the term HDRS may be any bracketed attribute name. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT HEADINGS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the titles that will appear at the top of each column in the List or Grid. The format of this row may be specified by an optional parameter following HEADERS in the above example.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT WIDTHS&#039;&#039;&#039; &lt;br /&gt;
| Specifies the number of characters in each column. For example if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT FORMS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the display characteristics of each column such as &amp;quot;C 12&amp;quot; or &amp;quot;PIC(z,zzz,zz#.##-)&amp;quot;. A &amp;quot;P&amp;quot; following the display parameter will cause the field to be protected and no data entry will be allowed in GRID mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; The field form array elements may also include a trailing comma followed by field attributes (e.g. color) that pertain to the column. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80, = [,fkey]&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt.&lt;br /&gt;
&lt;br /&gt;
==== Reading a Listview or Grid  ====&lt;br /&gt;
When using INPUT FIELDS to read from a 2D control, the leading attributes specification states the type of read operation and the trailing attributes specification is the type of cell or row selection to be performed. The third parameter can optionally specify NOWAIT or an FKEY value. If it is an FKEY value, it signifies that an FKEY event should be triggered when, in navigation mode, a selection is made by double clicking or pressing the Enter key.&lt;br /&gt;
 &lt;br /&gt;
===== Syntax  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Grid.png|950px]]&lt;br /&gt;
&lt;br /&gt;
=====Parameters=====&lt;br /&gt;
Quotation marks must surround the specifications, and individual parts must be separated by commas.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Row&#039;&#039;&#039; and &#039;&#039;&#039;Column&#039;&#039;&#039; specify the space where the grid or list begins.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;List&#039;&#039;&#039; or &#039;&#039;&#039;Grid&#039;&#039;&#039;, followed by rows and columns separated by a slash determine how big the list or grid is going to be. The main difference between a list and grid is that lists are for selection only while information can be added to grids directly. &lt;br /&gt;
&lt;br /&gt;
The following &#039;&#039;&#039;Read Types&#039;&#039;&#039; are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowCnt&#039;&#039;&#039; &lt;br /&gt;
| The number of rows specified.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowSub&#039;&#039;&#039; &lt;br /&gt;
| The subscripts of the specified rows.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Row&#039;&#039;&#039; &lt;br /&gt;
| Read all cells in each specified row.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Colcnt&#039;&#039;&#039; &lt;br /&gt;
|The number of columns established by the header arrays. e.g. INPUT FIELDS &amp;quot;row,col,LIST rows/cols, COLCNT, ALL&amp;quot; : numeric-variable&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sort_Order&#039;&#039;&#039; &lt;br /&gt;
|(4.3+) Provides a value of zero for each unsorted column and gives the ascending sequence of column sorts that have occurred. If a column has been reversed (double sorted) it&#039;s value will be negative. The selection typed used must be ALL. For example: INPUT FIELDS &amp;quot;row,col,GRID rows/cols, SORT_ORDER, ALL&amp;quot; : Mat NumArray, with the following history of sorting a four column GRID:&lt;br /&gt;
 column 1 (descending most recent)&lt;br /&gt;
 column 2 (ascending first sorted)&lt;br /&gt;
 column 3 (not sorted)&lt;br /&gt;
 column 4 (sorted ascending)&lt;br /&gt;
&lt;br /&gt;
SORT_ORDER would return-&lt;br /&gt;
 array(1) -&amp;gt;  -1&lt;br /&gt;
 array(2) -&amp;gt;   3&lt;br /&gt;
 array(3) -&amp;gt;   0&lt;br /&gt;
 array(4) -&amp;gt;   2&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;HEADERS&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) The read operation returns the original PRINT FIELDS HEADER values. For example:  INPUT FIELDS &amp;quot;row,col,LIST rows/cols, HEADERS,ALL,NOWAIT&amp;quot; : (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$) The selection type used must be ALL.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MASK&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) MASK can be used with Grids and Lists. As a READ type, this reads the display mask setting, including listviews that have been displayed according to a [[filter]] or filterbox. For example: INPUT FIELDS &amp;quot;row,col,LIST rows/cols,MASK [,NOWAIT]&amp;quot; : mask_array. The mask array affects only the user presentation and not the data. Use RANGE processing or the CHG selection type to selectively read from a 2D control.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These Read Types are valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cnt&#039;&#039;&#039; &lt;br /&gt;
| Specify the number of cells specified (see selection types below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sub&#039;&#039;&#039; &lt;br /&gt;
| Read the Cell Subscript Values (see example below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell&#039;&#039;&#039; &lt;br /&gt;
| Read each cell specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For the &amp;quot;Sel-type&amp;quot; parameter, the following selection types are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sel&#039;&#039;&#039; &lt;br /&gt;
| Read one or more selected items.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;SelOne&#039;&#039;&#039; &lt;br /&gt;
| Select only one item.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;All&#039;&#039;&#039; &lt;br /&gt;
| Read all items in the control (except headings).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cur&#039;&#039;&#039; &lt;br /&gt;
| Current cell or row number.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Next (4.2+)&#039;&#039;&#039; &lt;br /&gt;
| The cell the cursor is going to next if the user moved it using an arrow or a mouse click.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Range&#039;&#039;&#039;&lt;br /&gt;
|Specifies which portion of a 2D control is to be input. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell_Range&#039;&#039;&#039;&lt;br /&gt;
|A special type of output range. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This Selection Type is valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Chg&#039;&#039;&#039; &lt;br /&gt;
| All items changed since the last &#039;=&#039; populate or the last CHG retrieval of cell/row contents.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039; is an optional parameter to help the read. As of 4.3 it can be [[DISPLAYED_ORDER]]. #[[PIC]] or #[[FMT]] could be used. #PIC and #FMT allow numeric data from a string to be used. For example: &#039;&#039;&#039;DISPLAYED_ORDER&#039;&#039;&#039; Indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program (as of version 4.30). This reads the original row subscripts for all rows - in their present order - and only works with the ALL selection type.&lt;br /&gt;
&lt;br /&gt;
GRIDLINES makes LIST controls look like GRIDs with respect to the display of data (column and row separators).&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80,GRIDLINES&amp;quot;: 1 | 0 (on or off)&lt;br /&gt;
&lt;br /&gt;
The leading attribute values &amp;quot;^select&amp;quot; or &amp;quot;^deselect&amp;quot; may be specified to allow the pre-selection of GRID / LIST Elements: &lt;br /&gt;
 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,^select ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Fkey&#039;&#039;&#039; and &#039;&#039;&#039;Nowait&#039;&#039;&#039; parameters are optional. FKEY means that an FKEY event should be triggered when a selection is made by double clicking or pressing the Enter key, in navigation mode. Nowait simply means that it does not wait for user input.&lt;br /&gt;
&lt;br /&gt;
Following the ending quotation mark, a colon precedes the name of the I/O List.&lt;br /&gt;
&lt;br /&gt;
====DISPLAYED ORDER &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039;==== &lt;br /&gt;
&lt;br /&gt;
As of 4.30, [[DISPLAYED ORDER]] - indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program, for example:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROWSUB, ALL, DISPLAYED_ORDER, NOWAIT&amp;quot;: numeric-array &lt;br /&gt;
&lt;br /&gt;
This reads the original row subscripts for all rows in their present order. This qualifier works only with the ALL selection type. It may be used in conjunction with other qualifiers such as FKEY.&lt;br /&gt;
&lt;br /&gt;
==== Examples  ====&lt;br /&gt;
&lt;br /&gt;
===== LIST  =====&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL,FKEY&amp;quot;: avail_rows&amp;amp;nbsp;! selected row cnt&lt;br /&gt;
 00220&amp;amp;nbsp;! next INPUT operation does not wait for operator&lt;br /&gt;
 00230 MAT subscr(avail_rows)         &amp;amp;nbsp;! redimension with number of selected rows&lt;br /&gt;
 00240 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWSUB,SEL,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
&lt;br /&gt;
===== Uniform GRID  =====&lt;br /&gt;
Contains one data array and multiple columns&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CNT,CHG&amp;quot;: cells     &amp;amp;nbsp;! # of changed cells&lt;br /&gt;
 00220 MAT subscr(cells)                                        &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,SUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT data$(cells)                                         &amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CELL,CHG,NOWAIT&amp;quot;: MAT data$&amp;amp;nbsp;! read changes&lt;br /&gt;
&lt;br /&gt;
===== Row Oriented GRID  =====&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWCNT,CHG&amp;quot;: rows  &amp;amp;nbsp;! # of changed rows&lt;br /&gt;
 00220 MAT subscr(rows)                                  &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWSUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT NAME$(rows)&amp;amp;nbsp;: MAT CITY$(rows)&amp;amp;nbsp;: MAT AGE(rows)&amp;amp;nbsp;: MAT WEIGHT(rows)&amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROW,CHG,NOWAIT&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)  &amp;amp;nbsp;! read changed rows&lt;br /&gt;
&lt;br /&gt;
This brings us to the question of what is to be done with the information after it has been read. If it is to be stored in a file, then we should have included a hidden column with master file record numbers of the data in each row. This would support looping through the input array and rewriting the changed data. &lt;br /&gt;
&lt;br /&gt;
While LIST subscripts are expressed in terms of rows, GRID subscripts may be either. In a four column five row GRID, the cell at row three column two has a subscript of ten.&lt;br /&gt;
&lt;br /&gt;
==== Cell Subscript Values of a 5 x 4 GRID  ====&lt;br /&gt;
&lt;br /&gt;
 1   2   3   4&lt;br /&gt;
 5   6   7   8&lt;br /&gt;
 9  10  11  12&lt;br /&gt;
 13 14  15  16&lt;br /&gt;
 17 18  19  20&lt;br /&gt;
&lt;br /&gt;
==== Sample Program====&lt;br /&gt;
The following example shows use of LIST and GRID controls. There are seven parts in this program. &lt;br /&gt;
&lt;br /&gt;
The first part creates a LIST with 2 columns and 3 rows. On lines 300 - 500, column headings, widths, and form specifications are assigned to the corresponding matrices. Lines 600 and 700 place data into the matrices to be printed in the LIST. The HEADERS operation on line 800 sets the column headings, widths and form specs. Line 900 populates the LIST by row, which is the default. &lt;br /&gt;
&lt;br /&gt;
 00100 dim HEADINGS$(2), WIDTHS(2), FORMS$(2), NAMES$(3)*28, CITIES$(3)*18, DATA$(1)*80, SUBSCR(1)&lt;br /&gt;
 00200 print NEWPAGE&lt;br /&gt;
 00300 let HEADINGS$(1)=&amp;quot;Name&amp;quot;: let HEADINGS$(2)=&amp;quot;City&amp;quot;&lt;br /&gt;
 00400 let WIDTHS(1)=30&amp;amp;nbsp;: let WIDTHS(2)=20&lt;br /&gt;
 00500 let FORMS$(1)=&amp;quot;CC 28&amp;quot;&amp;amp;nbsp;: let FORMS$(2)=&amp;quot;CC 18&amp;quot;&lt;br /&gt;
 00600 let NAMES$(1)=&amp;quot;Stalin&amp;quot;&amp;amp;nbsp;: let NAMES$(2)=&amp;quot;Napoleon&amp;quot;&amp;amp;nbsp;: let NAMES$(3)=&amp;quot;Roosevelt&amp;quot;&lt;br /&gt;
 00700 let CITIES$(1)=&amp;quot;Moscow&amp;quot;&amp;amp;nbsp;: let CITIES$(2)=&amp;quot;Paris&amp;quot;&amp;amp;nbsp;: let CITIES$(3)=&amp;quot;Washington&amp;quot;&lt;br /&gt;
 00800 print fields &amp;quot;1,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 00900 print fields &amp;quot;1,1,list 8/60,=R&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
 01000 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at the end of list&amp;quot;&lt;br /&gt;
 01100 let KSTAT$(1)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output1.jpg]] &lt;br /&gt;
&lt;br /&gt;
The second part of the program demonstrates the use of the primary flag +, which adds to the end of any previously populated data. Line 01200 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 01300 places new data into the matrices to be printed in the LIST.&amp;lt;br&amp;gt; Line 01400 adds the new data to the LIST. &lt;br /&gt;
&lt;br /&gt;
 01200 mat NAMES$(UDIM(NAMES$)+1)&amp;amp;nbsp;: mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 01300 let NAMES$(4)=&amp;quot;Churchill&amp;quot;&amp;amp;nbsp;: let CITIES$(4)=&amp;quot;London&amp;quot;&lt;br /&gt;
 01400 print fields &amp;quot;1,1,list 8/60,+&amp;quot;: (MAT NAMES$(4:4),MAT CITIES$(4:4))&lt;br /&gt;
 01500 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Select rows to be read into a matrix&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output2.jpg]] &lt;br /&gt;
&lt;br /&gt;
The third part of the program demonstrates use of read types ROWSUB and ROWCNT and selection type SEL. Line 1600 inputs the number of selected rows into AVAIL_ROWS. The user may select rows using the mouse or the keyboard and SHIFT and CTRL keys. Line 1700 redimensions matrix SUBSCR to the number of selected rows. Line 1800 inputs the subscripts of the selected rows into matrix SUBSCR. Line 1900 performs a HEADERS operation for a new LIST using the same matrices as were used in the previous LIST. Line 2000 populates the first row of the new LIST with the data from the first selected row from the previous LIST using the primary flag =. If user selects more than one row, then lines 2100 - 2500 add the data from the selected rows using the primary flag +. &lt;br /&gt;
&lt;br /&gt;
 01600 input fields &amp;quot;1,1,list 8/60,ROWCNT,SEL&amp;quot;: AVAIL_ROWS&lt;br /&gt;
 01700 mat SUBSCR(AVAIL_ROWS)&lt;br /&gt;
 01800 input fields &amp;quot;1,1,list 8/60,rowsub,sel,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 01900 print fields &amp;quot;12,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 02000 print fields &amp;quot;12,1,list 8/60,=&amp;quot;: (MAT NAMES$(SUBSCR(1):SUBSCR(1)),MAT CITIES$(SUBSCR(1):SUBSCR(1)))&lt;br /&gt;
 02100 if UDIM(SUBSCR) &amp;amp;gt; 1 then&lt;br /&gt;
 02200   for I = 2 to UDIM(SUBSCR)&lt;br /&gt;
 02300     print fields &amp;quot;12,1,list 8/60,+&amp;quot;: ( MAT NAMES$(SUBSCR(I):SUBSCR(I)),MAT CITIES$(SUBSCR(I):SUBSCR(I)))&lt;br /&gt;
 02400   next I&lt;br /&gt;
 02500 end if&lt;br /&gt;
 02600 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at beginning of list&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output3.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fourth part of the program demonstrates the use of the primary flag -, which inserts in the beginning of any previously populated data. Line 2800 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 2900 places new data into the matrices to be printed in the LIST. Line 3000 adds the new data to the beginning of the LIST ahead of previously populated data. &lt;br /&gt;
&lt;br /&gt;
 02700 let KSTAT$(1)&lt;br /&gt;
 02800 mat NAMES$(UDIM(NAMES$)+1): mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 02900 let NAMES$(5)=&amp;quot;Castro&amp;quot;&amp;amp;nbsp;:! let CITIES$(5)=&amp;quot;Havana&amp;quot;&lt;br /&gt;
 03000 print fields &amp;quot;1,1,list 8/60,-&amp;quot;: (MAT NAMES$(5:5),MAT CITIES$(5:5))&lt;br /&gt;
 03100 print fields &amp;quot;9,1,C 60&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to populate list by column&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output4.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fifth part of the program, more specifically, Line 3300, demonstrates the use of the secondary flag C to populate the LIST by column. The primary flag = is also used in order to replace any previously populated data. &lt;br /&gt;
&lt;br /&gt;
 03200 let KSTAT$(1)&lt;br /&gt;
 03300 print fields &amp;quot;1,1,list 8/60,=C&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output5.jpg]] &lt;br /&gt;
&lt;br /&gt;
The sixth part of the program creates a GRID. The HEADERS operation on line 3600 uses the same HEADINGS$, WIDTHS, and FORMS$ as the previously constructed LISTs. Line 3700 sets CURFLD to be on the sixth cell of the GRID (this is discussed in the next section). Line 3800 populates the GRID. The user may change the contents of the cells. &lt;br /&gt;
&lt;br /&gt;
 03400 print fields &amp;quot;23,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to continue&amp;quot;: let KSTAT$(1)&lt;br /&gt;
 03500 print NEWPAGE&lt;br /&gt;
 03600 print fields &amp;quot;1,1,grid 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 03700 let CURFLD (1,6)&lt;br /&gt;
 03800 print fields &amp;quot;1,1,grid 8/60,=&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6.jpg]] &lt;br /&gt;
&lt;br /&gt;
The seventh part of the program demonstrates the use of read types CNT, CELL, and SUB and selection type CHG. Line 3900 counts the number of changed cells and inputs that number into variable CELLS. Line 4000 redimensions the matrix SUBSCR to the number of changed cells. Line 4100 inputs the changed cells into SUBSCR. Line 4200 redimensions the matrix DATA$.  Line 4300 inputs the subscripts of the changed cells into matrix DATA$. Line 4400 prints DATA$. &lt;br /&gt;
&lt;br /&gt;
 03900 input fields &amp;quot;1,1,grid 8/60,cnt,chg&amp;quot;: CELLS&lt;br /&gt;
 04000 mat SUBSCR(CELLS)&lt;br /&gt;
 04100 input fields &amp;quot;1,1,grid 8/60,sub,chg,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 04200 mat DATA$(CELLS)&lt;br /&gt;
 04300 input fields &amp;quot;1,1,grid 8/60,cell,chg,nowait&amp;quot;: MAT DATA$&lt;br /&gt;
 04400 print MAT DATA$&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6b.jpg]]&lt;br /&gt;
&lt;br /&gt;
===== Output of line 04400  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output7.jpg]]&lt;br /&gt;
&lt;br /&gt;
=== Displaying a List or Grid (Output Operations) ===&lt;br /&gt;
[[file:Grid2.png|900px]]&lt;br /&gt;
&lt;br /&gt;
To display a listview or a grid, you must set the headers first, using a special PRINT FIELDS operation.&lt;br /&gt;
&lt;br /&gt;
====HEADERS====&lt;br /&gt;
The HEADERS operation sets the column headings and widths. The corresponding input/output list value must be a parenthesized group of three arrays, for example: &lt;br /&gt;
&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$)&lt;br /&gt;
      - or -&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS,MAT FIELD_FORMS$)&lt;br /&gt;
&lt;br /&gt;
The [hdrs] notation refers to an optional CONFIG ATTRIBUTE [HDRS] specification for setting the appearance of the header row. In this case the [] brackets are required. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
MAT HEADINGS$ Contains the column titles that will be displayed at the top of each column. The font, color and shading of these titles can be set through the [HDRS] or similar substitution attribute. &lt;br /&gt;
&lt;br /&gt;
MAT WIDTHS specifies DISPLAYED Column Widths and is expressed as the number of character positions occupied by each column. Scrollbars are provided as needed to honor overall control size specified in the FIELDS specification. &lt;br /&gt;
&lt;br /&gt;
For example, if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area. &lt;br /&gt;
&lt;br /&gt;
Displayed widths of zero characters are allowed. This enables the use of hidden columns for storing things like record numbers and record keys. &lt;br /&gt;
&lt;br /&gt;
MAT FIELD_FORMS$ provides the BR FORM for each column. e.g. C 15 stipulates a maximum field capacity of 15. The actual displayed length is a function of the grid size and the column relative width. &lt;br /&gt;
&lt;br /&gt;
The field form array elements may also include a comma followed by leading field attributes (e.g. color) pertaining to the column.  &lt;br /&gt;
&lt;br /&gt;
The number of columns must be set with HEADERS prior to Populating a control (loading data into it).&lt;br /&gt;
&lt;br /&gt;
====MASKing====&lt;br /&gt;
&lt;br /&gt;
As of 4.3, LIST and GRID support the MASK operation, both in READs and Populating, as seen in this section. For example:&lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;row,col,LIST rows/cols,MASK&amp;quot; :  mask_array&lt;br /&gt;
&lt;br /&gt;
This restricts the rows (for both LIST and GRID) previously displayed to those corresponding to a “true” value in mask_array. A true value is represented in a numeric array as a value greater than zero. Negative values are not allowed in mask arrays. A string mask array may also be used with “T” and “F” values. The MASK stays in effect until 1) a new MASK is specified or 2) the contents of the control are changed with PRINT ( &amp;lt;nowiki&amp;gt;=, +, -,&amp;lt;/nowiki&amp;gt; see primary flags below).  Also, the mask array affects only the user presentation, not the result set. &lt;br /&gt;
&lt;br /&gt;
====Populating====&lt;br /&gt;
&lt;br /&gt;
The populate operation loads data into the control. In the following example four columns are loaded: &lt;br /&gt;
&lt;br /&gt;
 03010 PRINT FIELDS &amp;quot;10,20,LIST 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)&lt;br /&gt;
      - or -&lt;br /&gt;
 03020 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt. &lt;br /&gt;
&lt;br /&gt;
Permissible leading attribute values are:&lt;br /&gt;
&lt;br /&gt;
===== Primary Flags  =====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;=&#039;&#039;&#039; &lt;br /&gt;
| Replace any previous data&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;+&#039;&#039;&#039; &lt;br /&gt;
| Add to any previously populated data (this allows loading in chunks)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;-&#039;&#039;&#039; &lt;br /&gt;
| Insert data ahead of previously populated data (4.16+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Secondary Flags  ====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;R&#039;&#039;&#039; &lt;br /&gt;
| Load one row at a time (the default - use grouped IO parens)&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;C&#039;&#039;&#039; &lt;br /&gt;
| Load one column at a time - This is for loading multiple columns of the same data type&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;L&#039;&#039;&#039; &lt;br /&gt;
| Provide the FKEY (see INPUT below) or Enter interrupt if the user presses up arrow or page up in the first field, or down arrow or page down in the last field. Note that this is not specified in the individual field leading attributes.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;S&#039;&#039;&#039; &lt;br /&gt;
| Single click to activate an Enter or FKEY event (otherwise a double click is required) (4.17+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Note that the following example will NOT work- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=C&amp;quot;: MAT NAME$,MAT CITY$,MAT AGE,MAT WEIGHT&lt;br /&gt;
&lt;br /&gt;
The reason is that only MAT NAME$ will reach the list. MAT CITY$, MAT AGE and MAT WEIGHT will be associated with subsequent fields. Multiple arrays provided to a single control must be grouped with parentheses. &lt;br /&gt;
&lt;br /&gt;
Populating a two-dimensional object by row with grouped IO means NAME$(1) will go into (1,1), AGE(1) will go into (1,2) and WGT(1) will go into (1,3) and so on. If a single array (MAT DATA$) is specified instead of a group, MAT DATA$ is applied horizontally instead of vertically. So DATA$(1) - DATA$(3) will be the first row and DATA$(4) - DATA$(6) will be the next row and so on. &lt;br /&gt;
&lt;br /&gt;
Populating a two dimensional grid by column with an array named DATA$ means that DATA$(1) goes in (1,1) and DATA$(2) goes in (2,1) and DATA$(3) goes in (3,1). Therefore the first however many values of DATA refer to the first column and the second however many values of DATA refer to the second column. So if there are 3 columns and UDIM(DATA$) = 90 then DATA$(1)-DATA$(30) is the first column, and DATA$(31) - DATA$(60) is the second column and DATA$(61) - DATA$(90) is the last column. &lt;br /&gt;
&lt;br /&gt;
For example, using the following information, each example demonstrates how the grid will be filled:&lt;br /&gt;
&lt;br /&gt;
MAT NAME$ = George, Peter, Tom &lt;br /&gt;
&lt;br /&gt;
MAT CITY$ = Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
MAT AGE$ = 42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
MAT WEIGHT$ = 180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Peter, Detroit, 23, 212 &lt;br /&gt;
&lt;br /&gt;
Tom, Denver, 35, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =C&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
As you can see using C with grouped arrays is counter-intuitive and doesn&#039;t fit well. C is most useful with a single array that should be loaded vertically down several columns. &lt;br /&gt;
&lt;br /&gt;
===== Grid Validation  =====&lt;br /&gt;
&lt;br /&gt;
GRIDs are now validated as each cell is exited instead of when control is passed to the BR program after all data is entered.&lt;br /&gt;
&lt;br /&gt;
===== Color and Font changes in Cells  =====&lt;br /&gt;
&lt;br /&gt;
The attributes that determine font, color and style in each cell can be set for an entire column by including these parameters in the heading FORM array. Individual cells can then be changed using a PRINT statement. &lt;br /&gt;
&lt;br /&gt;
The format of the print statement is &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT #WINNO, fields &amp;quot;2,2,LIST 10/60,ATTR&amp;quot;:(mat start, mat end, mat attribute$)&lt;br /&gt;
&lt;br /&gt;
With the following parameter descriptions: &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat start&#039;&#039;&#039; &lt;br /&gt;
| contains the cell number(s) where the attribute chain begins,&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat end&#039;&#039;&#039; &lt;br /&gt;
| contains the last cell number where the attribute applies&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat attribute$&#039;&#039;&#039; &lt;br /&gt;
| contains the attribute specification that should be applied to the cell range(s) specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If the 2d control is a GRID, then mat Start and mat End refer to starting and ending Cell Numbers. If the 2d control is a listview, then mat Start and mat End refer to starting and ending Row Numbers. &lt;br /&gt;
&lt;br /&gt;
The attributes specified for any COLUMN can be overridden on a cell basis by specifying the starting cell number, ending cell number, and the overriding attributes in three arrays that are printed to the grid window with the same grid specificatoins and the key word &amp;quot;ATTR&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
 02420 PRINT #BLISTWIN,FIELDS BLISTSPEC$&amp;amp;amp;&amp;quot;,ATTR&amp;quot;: (MAT BROWS,MAT BROWE,MAT BATT$)&lt;br /&gt;
&lt;br /&gt;
In the above example, BLISTWIN is the window number, BLISTSPEC$ is the grid specification (&amp;quot;GRID 10/40&amp;quot; for example), BROWS is an array holding the starting cell number, BROWE is an array holding the ending cell number, and BATT$ is an array holding the overriding attributes. In a list the attributes of the first cell in the row controls the appearance of the entire row.&lt;br /&gt;
&lt;br /&gt;
 01500 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The above example overrides the attributes of a range of cells/rows for a GRID/LIST display. This allows you to shade or otherwise alter the display of a range of cells / rows in a 2D control.&lt;br /&gt;
&lt;br /&gt;
====Aggregate Sorting====&lt;br /&gt;
BR supports aggregated sorting for LISTs and GRIDs. This means when clicking &lt;br /&gt;
on various column headings or programmatically sorting columns, fields of &lt;br /&gt;
equal values retain their previous order within their new groupings (4.2).&lt;br /&gt;
&lt;br /&gt;
==== Numeric Column Sorting  ====&lt;br /&gt;
&lt;br /&gt;
2D controls now facilitate numeric column sorting. This works well in conjunction with the new [[Date (Format Specification)|DATE]] field format (see release notes [[4.16]]) where the data is stored as day of century, but is displayed as a formatted date. It also works with all numeric columns.&lt;br /&gt;
&lt;br /&gt;
If a listview columns form spec if given as DATE(mm/dd/ccyy), for example, then any time that column is sorted, either as a result of the user clicking on the column heading, or the program giving the sort command (shown below), the dates are properly sorted even though they&#039;re displayed as mm/dd/ccyy. For this to work, the data has to be given in the julian days format, see the [[DAYS]] function for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In versions 4.2 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
To sort in reverse order, sort the column twice:&lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { same column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended in version 4.3 to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns.&lt;br /&gt;
&lt;br /&gt;
==== NOSORT for Columns  ====&lt;br /&gt;
As of 4.2, the &#039;&#039;&#039;NoSort&#039;&#039;&#039; parameter is used to prevent users from sorting columns of a Grid or List. &lt;br /&gt;
&lt;br /&gt;
For the statement: &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$) &lt;br /&gt;
&lt;br /&gt;
The field attribute &amp;quot;^nosort&amp;quot; appearing in the MAT FIELD_FORM$ prevents the sorting of a grid or listview in response to the user clicking on the corresponding column header. This does not prevent programs from sorting on those columns.&lt;br /&gt;
&lt;br /&gt;
==== Range Input  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are used in versions 4.3 and higher. In these examples BR will redimension the receiving arrays as needed: &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, CELL, RANGE&amp;quot; :&lt;br /&gt;
             start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This reads the specified range of cells. BR redimensions MAT Data$ as needed. Note that CELL may now be used with LIST. Previously, LISTs were only addressable by row. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot; :&lt;br /&gt;
             (start:=7), (end:=11), ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads the cells in rows 7 through 11. The receiving arrays are re-dimensioned as appropriate. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,GRID rows/cols, ROW, RANGE&amp;quot;:&lt;br /&gt;
             MAT start, MAT end, ( MAT Data1$, MAT Data2$, MAT Data3 )&lt;br /&gt;
&lt;br /&gt;
This reads one or more ranges of rows. &lt;br /&gt;
&lt;br /&gt;
A more detailed example of this is: &lt;br /&gt;
&lt;br /&gt;
 100 ! create and populate a LIST control &lt;br /&gt;
 200 MAT START(3) : MAT END(3)&lt;br /&gt;
 210 READ MAT START, MAT END&lt;br /&gt;
 220 DATA 7,21,33&lt;br /&gt;
 230 DATA 11,21,38&lt;br /&gt;
 240 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot;&amp;amp;nbsp;: MAT START,&lt;br /&gt;
            MAT END, ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads 12 rows of data ( row 7-11, row 21 and rows 33-38 ). &lt;br /&gt;
&lt;br /&gt;
==== Range Output  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions [[4.3]] and higher. By default, RANGE output refers to rows. The special keyword CELL_RANGE is used to denote the specification of cell ranges. Additionally, the use of scalars versus arrays for start and end values determines important characteristics of the output process. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Using Scalars For Range Specification &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,GRID 10/75, RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This replaces the values in rows numbered &#039;start&#039; through &#039;end&#039; with the data in MAT Data$. The size of MAT Data$ must be a multiple of the number of columns in the GRID or an error is generated. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$,                                   MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
This replaces the values in ROWs numbered &#039;start&#039; through &#039;end&#039; with the data from MATs NAME$, CITY$, AGE and WEIGHT. The data arrays must all be dimensioned the same. &lt;br /&gt;
&lt;br /&gt;
==== Insertion and Deletion with RANGE ====&lt;br /&gt;
&lt;br /&gt;
The number of rows being output do not need to match the number of rows being replaced. To delete a range of rows, output one or more grouped arrays with zero elements. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. Using the following statement, various scenarios are described. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays have been DIMed to nine elements&lt;br /&gt;
 Result- Nine rows replace five, and the total content of the control is expanded by 4 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays are DIMed to zero elements&lt;br /&gt;
 Result- Five rows are deleted, and the total size of the control is reduced by 5 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=0 (anything less than 7), and the arrays are DIMed to support three rows&lt;br /&gt;
 Result- Three rows are inserted ahead of row seven and the total content of the control is expanded by three rows&lt;br /&gt;
&lt;br /&gt;
 start=5000, end={any value}, the control only has 482 rows, and the source arrays are DIMed to support eleven rows&lt;br /&gt;
 Result- Eleven rows are appended to the end of the control and become rows 483 through 493.&lt;br /&gt;
&lt;br /&gt;
==== Outputting Ranges of Cells  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. &lt;br /&gt;
&lt;br /&gt;
Ranges of cells may be output in conjunction with the CELL_RANGE keyword. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, CELL_RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
                                - or -&lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
In this case, start and end specify cells instead of rows. If insertion or deletion is indicated by dimensioning the data arrays to greater or fewer elements than are being replaced, then the data must be a multiple of the number of columns. Insertion and deletion is only valid in terms of rows, even when cell subscripts are used to specify ranges. In such cases, if the cell subscripts are not on row boundaries, an error is generated. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, start, Data$&lt;br /&gt;
&lt;br /&gt;
In this example, the value in one cell is replaced with the content of a scalar.&lt;br /&gt;
&lt;br /&gt;
==== Using Arrays For Range Specification  ====&lt;br /&gt;
&lt;br /&gt;
If the start and end specifications are array denoting multiple ranges, there must be a one to one correspondence between the number of rows specified and those in the data. This method implies replacement only and insertion or deletion is not allowed. &lt;br /&gt;
&lt;br /&gt;
The data flow that this feature was designed to support is one where the user is presented with a LIST or GRID where multiple rows have been either selected or changed before returning control to the program and the program is responding by updating something on those rows. &lt;br /&gt;
&lt;br /&gt;
The program begins by presenting a 2D control to the user and reading the the control with type ROWSUB or SUB. Type SUB only works for GRIDs where all colmns have the same data type. Of course the subscripts are read into a numeric array which BR redimensions as appropriate. Then the program reads the changed or selected data with NOWAIT. (This resets the CHG flags in the control.) The program then changes either row (ROWSUB) or cell (SUB) data and outputs the results using the subscript array as both the start and end specification. Other scenarios are possible but this is the primary intended use. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher: &lt;br /&gt;
&lt;br /&gt;
  100 ! create and populate a GRID --&lt;br /&gt;
  200 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROWSUB,CHG&amp;quot;: MAT Rowsubs&lt;br /&gt;
         (Reading subscripts does not reset the CHG flags in the control.)&lt;br /&gt;
  210 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROW,CHG,NOWAIT&amp;quot;: ( MAT Data1$,&lt;br /&gt;
        MAT Data2, MAT Data3$ )&lt;br /&gt;
          BR redimensions the receiving arrays as needed.&lt;br /&gt;
         (Reading the data also resets the CHG flags in the control.)&lt;br /&gt;
&lt;br /&gt;
  220 ! process the changed rows now present in the data arrays --&lt;br /&gt;
  300 PRINT FIELDS &amp;quot;row,col,GRID rows/cols,RANGE&amp;quot;: MAT Rowsubs,&lt;br /&gt;
                   MAT Rowsubs, ( MAT Data1$, MAT Data2, MAT Data3$ )&lt;br /&gt;
&lt;br /&gt;
This outputs the updated rows. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Grid and List BR_VB Similarities  ====&lt;br /&gt;
Before introducing grids and lists in BR, similar effects could be achieved using BR_VB to work with Visual Basic. If you were familiar with BR_VB, the following notes may be of interest:&lt;br /&gt;
&lt;br /&gt;
Grid and list controls work like the BR_VB interface except headers now specify the form of each column and a new input type has been added: &lt;br /&gt;
&lt;br /&gt;
A multi column LISTview- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS[,hdrs][,fkey]&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FORMS$) &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note-&#039;&#039;&#039; Widths are expressed in character positions, not percentages like they are in BR_VB. &lt;br /&gt;
&lt;br /&gt;
The populate operation- &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
Read-Ctl type: CNT (in place of CELLROWSUB) returns a single numeric value which is the number of subscripts available to read. In the case of grids, this is the number of cells. In the case of LISTviews, this is the number of rows. &lt;br /&gt;
&lt;br /&gt;
 00110 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL&amp;quot;: avail_rows&amp;amp;nbsp;! # of selected rows&lt;br /&gt;
 00120 MAT DATA$(3 * avail_rows)         &amp;amp;nbsp;! redimension.. 3 cols x selected rows&lt;br /&gt;
 00130 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROW,SEL,NOWAIT&amp;quot;: MAT DATA$&amp;amp;nbsp;! read rows&lt;br /&gt;
&lt;br /&gt;
====See Also: ====&lt;br /&gt;
* [[Grids Tutorial]]&lt;br /&gt;
* [[0886]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Widget]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11439</id>
		<title>Grid and List</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=Grid_and_List&amp;diff=11439"/>
		<updated>2024-02-05T05:03:13Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* GRID CURSOR MOVEMENT */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Grid&#039;&#039;&#039; and &#039;&#039;&#039;List&#039;&#039;&#039; (a.k.a. &#039;&#039;&#039;ListView&#039;&#039;&#039;) are [[Two dimensional controls]], which means that they contain rows and columns. GRIDs are normally used for data entry, while LISTs are used only for selection. In Business Rules, outputting to these controls is done with [[Print Fields]] and inputting from them is done with [[Input Fields]]. Each field specification pertains to one [[Control]]. However, a single FIELD specification can pertain to a parenthesized group of arrays. When using Grids and Lists, first the header must be populated, and then the data within the rows. &lt;br /&gt;
&lt;br /&gt;
The [[INPUT FIELDS]] specification states Grid or List as the field type. The display area is specified in terms of rows and columns separated by a slash. The following parameter, instead of being leading field attributes, is a secondary keyword indicating the type (CNT/SUB/CELL/ROWCNT/ROWSUB/ROW) of output or input operation to be performed. The next parameter further qualifies the IO operation (CHG/SEL/ALL/CUR/NEXT). Normally this trailing attribute is an [[FKEY]] value which is shifted right one parameter in this context. &lt;br /&gt;
&lt;br /&gt;
It is useful to work with [[2D Controls]] in terms of rows versus cells, particularly when the columns are dissimilar. A complete set of row oriented parameters are provided for that purpose. Output operations support the mass populating of 2D controls row by row. And input operations can be row oriented as well. The keywords associated with row processing are R, ROWCNT, ROWSUB, and ROW. &lt;br /&gt;
&lt;br /&gt;
RINPUT does not work with 2D controls because the output statements are somewhat different from the input statements. The output consists of setting up the columns, including providing the column headings, and populating the control with data. The input consists of identifying what has changed in a manner that enables selective data retrieval and corresponding file updating. &lt;br /&gt;
&lt;br /&gt;
If the user clicks on a column heading the GRID or LIST will be sorted on that column. Sorting is done in terms of rows. Such sorting of these controls has no affect on the BR program. The information returned to BR will be as though no sorting were performed. If a control&#039;s population is increased by populating with the plus (+) flag, the control will be resequenced back to its original order before the data is added. One way a program can restore the original displayed order of a GRID or LIST is to populate it incrementally (+) with no data. As of version 4.1 and higher, if a GRID input is attempted on a protected field, BR issues a Bell. &lt;br /&gt;
&lt;br /&gt;
Shift+PgUp and Shift+PgDn selects within a List/Grid. &lt;br /&gt;
&lt;br /&gt;
Grids and lists are compatible with the [[FILTER]] field, which works like a search bar. &lt;br /&gt;
&lt;br /&gt;
In GRIDs and LISTs only, string arrays may be used to process numeric values. BR automatically performs [[VAL]] and [[STR]] conversions as needed.  If string data is passed to a numeric field type such as N or DATE then it is automatically converted to numeric form for internal processing (4.2).&lt;br /&gt;
&lt;br /&gt;
As of Business Rules! versions [[4.3]]+, arrays are automatically resized when receiving data from 2D INPUT operations. This also applies to grouped arrays. Automatic resizing only applies to one dimensional arrays and does not occur when INPUTing into two dimensional arrays. For example, where all arrays are one dimensional and may have the incorrect number of elements:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, ALL&amp;quot;&amp;amp;nbsp;: ( MAT Array1$,&amp;lt;span style=&amp;quot;font-family: monospace;&amp;quot;&amp;gt; &amp;lt;/span&amp;gt;MAT Array2$, MAT Array3, MAT Array4, MAT Array5$ )  &lt;br /&gt;
&lt;br /&gt;
====FKey Processing====&lt;br /&gt;
&lt;br /&gt;
An [[FKey]] value can be associated with a LIST or GRID control by specifying the FKey number during either output or input operations. Once an Fkey value is specified, the control retains the setting until it is reset. An FKey value can be cleared by specifying an Fkey value of minus one (-1). &lt;br /&gt;
&lt;br /&gt;
When a LIST or GRID has an FKey value set processing is dependent on whether or not the 2D control is being processed by an INPUT FIELDS statement: &lt;br /&gt;
&lt;br /&gt;
*Displayed but inactive- Single clicking any cell produces the FKey interrupt. &lt;br /&gt;
*Active (participating in Input Fields)- Double clicking any cell produces an FKey completion.&lt;br /&gt;
&lt;br /&gt;
Formerly, [[CURROW]] and [[CURCOL]] represented the character position where the cursor was when FIELDS processing is completed. This is still true except when the most recent field processed is a [[2D control]]. &lt;br /&gt;
&lt;br /&gt;
When FIELDS processing ends and control is returned to the program while the &#039;current&#039; control is of type LIST or GRID, CURROW and CURCOL are set to the current cell row and column within the 2D control instead of the character position relative to the window.&lt;br /&gt;
&lt;br /&gt;
====GRID CURSOR MOVEMENT====&lt;br /&gt;
When field + or - is keyed BR always returns fkey values of 114 or 115 in both navigation and edit mode and for any type of data. &lt;br /&gt;
(This assumes the X attribute or some other attribute returns control to the program.)&lt;br /&gt;
&lt;br /&gt;
Default cursor positioning for Field +/- keys is to perform a down arrow operation, and the Enter key defaults to NONE (no movement). &lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- forces the signing of a numeric field, whether or not the field type is PIC.&lt;br /&gt;
&lt;br /&gt;
In edit mode, Field +/- also right truncates any character or numeric data before exiting the field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A Config statement can be used to override (control) grid cursor movement. When this is used, both Field +/- and Enter produce the same cursor movement:&lt;br /&gt;
&lt;br /&gt;
 GRID_CURSOR_MOVE  DOWN | RIGHT | NONE | DEFAULT&lt;br /&gt;
&lt;br /&gt;
This determines the field cursor position after keying Enter or Field +/-. Both navigation and edit mode produce the same resulting cursor position.&lt;br /&gt;
&lt;br /&gt;
====Restoring a User Sorted 2D Control====&lt;br /&gt;
&lt;br /&gt;
In versions 4.3 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns. &lt;br /&gt;
&lt;br /&gt;
==== A multi column LISTview  ====&lt;br /&gt;
&lt;br /&gt;
 01000 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS,[HDRS][,fkey]&amp;quot;: (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Widths are expressed in character positions.&lt;br /&gt;
&lt;br /&gt;
The [HDRS] notation refers to an optional CONFIG ATTRIBUTE HDRS specification for setting the appearance of the header row. In this case the  brackets [] are required and the term HDRS may be any bracketed attribute name. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT HEADINGS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the titles that will appear at the top of each column in the List or Grid. The format of this row may be specified by an optional parameter following HEADERS in the above example.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT WIDTHS&#039;&#039;&#039; &lt;br /&gt;
| Specifies the number of characters in each column. For example if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MAT FORMS$&#039;&#039;&#039; &lt;br /&gt;
| Specifies the display characteristics of each column such as &amp;quot;C 12&amp;quot; or &amp;quot;PIC(z,zzz,zz#.##-)&amp;quot;. A &amp;quot;P&amp;quot; following the display parameter will cause the field to be protected and no data entry will be allowed in GRID mode.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; The field form array elements may also include a trailing comma followed by field attributes (e.g. color) that pertain to the column. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80, = [,fkey]&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt.&lt;br /&gt;
&lt;br /&gt;
==== Reading a Listview or Grid  ====&lt;br /&gt;
When using INPUT FIELDS to read from a 2D control, the leading attributes specification states the type of read operation and the trailing attributes specification is the type of cell or row selection to be performed. The third parameter can optionally specify NOWAIT or an FKEY value. If it is an FKEY value, it signifies that an FKEY event should be triggered when, in navigation mode, a selection is made by double clicking or pressing the Enter key.&lt;br /&gt;
 &lt;br /&gt;
===== Syntax  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Grid.png|950px]]&lt;br /&gt;
&lt;br /&gt;
=====Parameters=====&lt;br /&gt;
Quotation marks must surround the specifications, and individual parts must be separated by commas.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Row&#039;&#039;&#039; and &#039;&#039;&#039;Column&#039;&#039;&#039; specify the space where the grid or list begins.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;List&#039;&#039;&#039; or &#039;&#039;&#039;Grid&#039;&#039;&#039;, followed by rows and columns separated by a slash determine how big the list or grid is going to be. The main difference between a list and grid is that lists are for selection only while information can be added to grids directly. &lt;br /&gt;
&lt;br /&gt;
The following &#039;&#039;&#039;Read Types&#039;&#039;&#039; are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowCnt&#039;&#039;&#039; &lt;br /&gt;
| The number of rows specified.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;RowSub&#039;&#039;&#039; &lt;br /&gt;
| The subscripts of the specified rows.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Row&#039;&#039;&#039; &lt;br /&gt;
| Read all cells in each specified row.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Colcnt&#039;&#039;&#039; &lt;br /&gt;
|The number of columns established by the header arrays. e.g. INPUT FIELDS &amp;quot;row,col,LIST rows/cols, COLCNT, ALL&amp;quot; : numeric-variable&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sort_Order&#039;&#039;&#039; &lt;br /&gt;
|(4.3+) Provides a value of zero for each unsorted column and gives the ascending sequence of column sorts that have occurred. If a column has been reversed (double sorted) it&#039;s value will be negative. The selection typed used must be ALL. For example: INPUT FIELDS &amp;quot;row,col,GRID rows/cols, SORT_ORDER, ALL&amp;quot; : Mat NumArray, with the following history of sorting a four column GRID:&lt;br /&gt;
 column 1 (descending most recent)&lt;br /&gt;
 column 2 (ascending first sorted)&lt;br /&gt;
 column 3 (not sorted)&lt;br /&gt;
 column 4 (sorted ascending)&lt;br /&gt;
&lt;br /&gt;
SORT_ORDER would return-&lt;br /&gt;
 array(1) -&amp;gt;  -1&lt;br /&gt;
 array(2) -&amp;gt;   3&lt;br /&gt;
 array(3) -&amp;gt;   0&lt;br /&gt;
 array(4) -&amp;gt;   2&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;HEADERS&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) The read operation returns the original PRINT FIELDS HEADER values. For example:  INPUT FIELDS &amp;quot;row,col,LIST rows/cols, HEADERS,ALL,NOWAIT&amp;quot; : (MAT HEADINGS$,MAT WIDTHS, MAT FORMS$) The selection type used must be ALL.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;MASK&#039;&#039;&#039;&lt;br /&gt;
|(As of 4.30) MASK can be used with Grids and Lists. As a READ type, this reads the display mask setting, including listviews that have been displayed according to a [[filter]] or filterbox. For example: INPUT FIELDS &amp;quot;row,col,LIST rows/cols,MASK [,NOWAIT]&amp;quot; : mask_array. The mask array affects only the user presentation and not the data. Use RANGE processing or the CHG selection type to selectively read from a 2D control.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These Read Types are valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cnt&#039;&#039;&#039; &lt;br /&gt;
| Specify the number of cells specified (see selection types below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sub&#039;&#039;&#039; &lt;br /&gt;
| Read the Cell Subscript Values (see example below).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell&#039;&#039;&#039; &lt;br /&gt;
| Read each cell specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For the &amp;quot;Sel-type&amp;quot; parameter, the following selection types are valid for both grids and lists:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Sel&#039;&#039;&#039; &lt;br /&gt;
| Read one or more selected items.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;SelOne&#039;&#039;&#039; &lt;br /&gt;
| Select only one item.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;All&#039;&#039;&#039; &lt;br /&gt;
| Read all items in the control (except headings).&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cur&#039;&#039;&#039; &lt;br /&gt;
| Current cell or row number.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Next (4.2+)&#039;&#039;&#039; &lt;br /&gt;
| The cell the cursor is going to next if the user moved it using an arrow or a mouse click.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Range&#039;&#039;&#039;&lt;br /&gt;
|Specifies which portion of a 2D control is to be input. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Cell_Range&#039;&#039;&#039;&lt;br /&gt;
|A special type of output range. (As of 4.3) [[#Range Input|See Below]].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This Selection Type is valid for Grids only:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;Chg&#039;&#039;&#039; &lt;br /&gt;
| All items changed since the last &#039;=&#039; populate or the last CHG retrieval of cell/row contents.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039; is an optional parameter to help the read. As of 4.3 it can be [[DISPLAYED_ORDER]]. #[[PIC]] or #[[FMT]] could be used. #PIC and #FMT allow numeric data from a string to be used. For example: &#039;&#039;&#039;DISPLAYED_ORDER&#039;&#039;&#039; Indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program (as of version 4.30). This reads the original row subscripts for all rows - in their present order - and only works with the ALL selection type.&lt;br /&gt;
&lt;br /&gt;
GRIDLINES makes LIST controls look like GRIDs with respect to the display of data (column and row separators).&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,LIST 10/80,GRIDLINES&amp;quot;: 1 | 0 (on or off)&lt;br /&gt;
&lt;br /&gt;
The leading attribute values &amp;quot;^select&amp;quot; or &amp;quot;^deselect&amp;quot; may be specified to allow the pre-selection of GRID / LIST Elements: &lt;br /&gt;
 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,^select ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;Fkey&#039;&#039;&#039; and &#039;&#039;&#039;Nowait&#039;&#039;&#039; parameters are optional. FKEY means that an FKEY event should be triggered when a selection is made by double clicking or pressing the Enter key, in navigation mode. Nowait simply means that it does not wait for user input.&lt;br /&gt;
&lt;br /&gt;
Following the ending quotation mark, a colon precedes the name of the I/O List.&lt;br /&gt;
&lt;br /&gt;
====DISPLAYED ORDER &#039;&#039;&#039;Read Qualifier&#039;&#039;&#039;==== &lt;br /&gt;
&lt;br /&gt;
As of 4.30, [[DISPLAYED ORDER]] - indicates that the read operation is to not restore the data into it&#039;s original order before returning the results to the program, for example:&lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROWSUB, ALL, DISPLAYED_ORDER, NOWAIT&amp;quot;: numeric-array &lt;br /&gt;
&lt;br /&gt;
This reads the original row subscripts for all rows in their present order. This qualifier works only with the ALL selection type. It may be used in conjunction with other qualifiers such as FKEY.&lt;br /&gt;
&lt;br /&gt;
==== Examples  ====&lt;br /&gt;
&lt;br /&gt;
===== LIST  =====&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL,FKEY&amp;quot;: avail_rows&amp;amp;nbsp;! selected row cnt&lt;br /&gt;
 00220&amp;amp;nbsp;! next INPUT operation does not wait for operator&lt;br /&gt;
 00230 MAT subscr(avail_rows)         &amp;amp;nbsp;! redimension with number of selected rows&lt;br /&gt;
 00240 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWSUB,SEL,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
&lt;br /&gt;
===== Uniform GRID  =====&lt;br /&gt;
Contains one data array and multiple columns&lt;br /&gt;
&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CNT,CHG&amp;quot;: cells     &amp;amp;nbsp;! # of changed cells&lt;br /&gt;
 00220 MAT subscr(cells)                                        &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,SUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT data$(cells)                                         &amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,CELL,CHG,NOWAIT&amp;quot;: MAT data$&amp;amp;nbsp;! read changes&lt;br /&gt;
&lt;br /&gt;
===== Row Oriented GRID  =====&lt;br /&gt;
 00210 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWCNT,CHG&amp;quot;: rows  &amp;amp;nbsp;! # of changed rows&lt;br /&gt;
 00220 MAT subscr(rows)                                  &amp;amp;nbsp;! redimension&lt;br /&gt;
 00230 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROWSUB,CHG,NOWAIT&amp;quot;: MAT subscr&amp;amp;nbsp;! read subscripts&lt;br /&gt;
 00240 MAT NAME$(rows)&amp;amp;nbsp;: MAT CITY$(rows)&amp;amp;nbsp;: MAT AGE(rows)&amp;amp;nbsp;: MAT WEIGHT(rows)&amp;amp;nbsp;! redimension&lt;br /&gt;
 00250 INPUT FIELDS &amp;quot;10,20,GRID 10/80,ROW,CHG,NOWAIT&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)  &amp;amp;nbsp;! read changed rows&lt;br /&gt;
&lt;br /&gt;
This brings us to the question of what is to be done with the information after it has been read. If it is to be stored in a file, then we should have included a hidden column with master file record numbers of the data in each row. This would support looping through the input array and rewriting the changed data. &lt;br /&gt;
&lt;br /&gt;
While LIST subscripts are expressed in terms of rows, GRID subscripts may be either. In a four column five row GRID, the cell at row three column two has a subscript of ten.&lt;br /&gt;
&lt;br /&gt;
==== Cell Subscript Values of a 5 x 4 GRID  ====&lt;br /&gt;
&lt;br /&gt;
 1   2   3   4&lt;br /&gt;
 5   6   7   8&lt;br /&gt;
 9  10  11  12&lt;br /&gt;
 13 14  15  16&lt;br /&gt;
 17 18  19  20&lt;br /&gt;
&lt;br /&gt;
==== Sample Program====&lt;br /&gt;
The following example shows use of LIST and GRID controls. There are seven parts in this program. &lt;br /&gt;
&lt;br /&gt;
The first part creates a LIST with 2 columns and 3 rows. On lines 300 - 500, column headings, widths, and form specifications are assigned to the corresponding matrices. Lines 600 and 700 place data into the matrices to be printed in the LIST. The HEADERS operation on line 800 sets the column headings, widths and form specs. Line 900 populates the LIST by row, which is the default. &lt;br /&gt;
&lt;br /&gt;
 00100 dim HEADINGS$(2), WIDTHS(2), FORMS$(2), NAMES$(3)*28, CITIES$(3)*18, DATA$(1)*80, SUBSCR(1)&lt;br /&gt;
 00200 print NEWPAGE&lt;br /&gt;
 00300 let HEADINGS$(1)=&amp;quot;Name&amp;quot;: let HEADINGS$(2)=&amp;quot;City&amp;quot;&lt;br /&gt;
 00400 let WIDTHS(1)=30&amp;amp;nbsp;: let WIDTHS(2)=20&lt;br /&gt;
 00500 let FORMS$(1)=&amp;quot;CC 28&amp;quot;&amp;amp;nbsp;: let FORMS$(2)=&amp;quot;CC 18&amp;quot;&lt;br /&gt;
 00600 let NAMES$(1)=&amp;quot;Stalin&amp;quot;&amp;amp;nbsp;: let NAMES$(2)=&amp;quot;Napoleon&amp;quot;&amp;amp;nbsp;: let NAMES$(3)=&amp;quot;Roosevelt&amp;quot;&lt;br /&gt;
 00700 let CITIES$(1)=&amp;quot;Moscow&amp;quot;&amp;amp;nbsp;: let CITIES$(2)=&amp;quot;Paris&amp;quot;&amp;amp;nbsp;: let CITIES$(3)=&amp;quot;Washington&amp;quot;&lt;br /&gt;
 00800 print fields &amp;quot;1,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 00900 print fields &amp;quot;1,1,list 8/60,=R&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
 01000 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at the end of list&amp;quot;&lt;br /&gt;
 01100 let KSTAT$(1)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output1.jpg]] &lt;br /&gt;
&lt;br /&gt;
The second part of the program demonstrates the use of the primary flag +, which adds to the end of any previously populated data. Line 01200 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 01300 places new data into the matrices to be printed in the LIST.&amp;lt;br&amp;gt; Line 01400 adds the new data to the LIST. &lt;br /&gt;
&lt;br /&gt;
 01200 mat NAMES$(UDIM(NAMES$)+1)&amp;amp;nbsp;: mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 01300 let NAMES$(4)=&amp;quot;Churchill&amp;quot;&amp;amp;nbsp;: let CITIES$(4)=&amp;quot;London&amp;quot;&lt;br /&gt;
 01400 print fields &amp;quot;1,1,list 8/60,+&amp;quot;: (MAT NAMES$(4:4),MAT CITIES$(4:4))&lt;br /&gt;
 01500 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Select rows to be read into a matrix&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output2.jpg]] &lt;br /&gt;
&lt;br /&gt;
The third part of the program demonstrates use of read types ROWSUB and ROWCNT and selection type SEL. Line 1600 inputs the number of selected rows into AVAIL_ROWS. The user may select rows using the mouse or the keyboard and SHIFT and CTRL keys. Line 1700 redimensions matrix SUBSCR to the number of selected rows. Line 1800 inputs the subscripts of the selected rows into matrix SUBSCR. Line 1900 performs a HEADERS operation for a new LIST using the same matrices as were used in the previous LIST. Line 2000 populates the first row of the new LIST with the data from the first selected row from the previous LIST using the primary flag =. If user selects more than one row, then lines 2100 - 2500 add the data from the selected rows using the primary flag +. &lt;br /&gt;
&lt;br /&gt;
 01600 input fields &amp;quot;1,1,list 8/60,ROWCNT,SEL&amp;quot;: AVAIL_ROWS&lt;br /&gt;
 01700 mat SUBSCR(AVAIL_ROWS)&lt;br /&gt;
 01800 input fields &amp;quot;1,1,list 8/60,rowsub,sel,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 01900 print fields &amp;quot;12,1,list 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 02000 print fields &amp;quot;12,1,list 8/60,=&amp;quot;: (MAT NAMES$(SUBSCR(1):SUBSCR(1)),MAT CITIES$(SUBSCR(1):SUBSCR(1)))&lt;br /&gt;
 02100 if UDIM(SUBSCR) &amp;amp;gt; 1 then&lt;br /&gt;
 02200   for I = 2 to UDIM(SUBSCR)&lt;br /&gt;
 02300     print fields &amp;quot;12,1,list 8/60,+&amp;quot;: ( MAT NAMES$(SUBSCR(I):SUBSCR(I)),MAT CITIES$(SUBSCR(I):SUBSCR(I)))&lt;br /&gt;
 02400   next I&lt;br /&gt;
 02500 end if&lt;br /&gt;
 02600 print fields &amp;quot;9,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to insert at beginning of list&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output3.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fourth part of the program demonstrates the use of the primary flag -, which inserts in the beginning of any previously populated data. Line 2800 redimensions matrices NAMES$ and CITIES$ to allow room for one extra item in each of them. Line 2900 places new data into the matrices to be printed in the LIST. Line 3000 adds the new data to the beginning of the LIST ahead of previously populated data. &lt;br /&gt;
&lt;br /&gt;
 02700 let KSTAT$(1)&lt;br /&gt;
 02800 mat NAMES$(UDIM(NAMES$)+1): mat CITIES$(UDIM(CITIES$)+1)&lt;br /&gt;
 02900 let NAMES$(5)=&amp;quot;Castro&amp;quot;&amp;amp;nbsp;:! let CITIES$(5)=&amp;quot;Havana&amp;quot;&lt;br /&gt;
 03000 print fields &amp;quot;1,1,list 8/60,-&amp;quot;: (MAT NAMES$(5:5),MAT CITIES$(5:5))&lt;br /&gt;
 03100 print fields &amp;quot;9,1,C 60&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to populate list by column&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output4.jpg]] &lt;br /&gt;
&lt;br /&gt;
The fifth part of the program, more specifically, Line 3300, demonstrates the use of the secondary flag C to populate the LIST by column. The primary flag = is also used in order to replace any previously populated data. &lt;br /&gt;
&lt;br /&gt;
 03200 let KSTAT$(1)&lt;br /&gt;
 03300 print fields &amp;quot;1,1,list 8/60,=C&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output5.jpg]] &lt;br /&gt;
&lt;br /&gt;
The sixth part of the program creates a GRID. The HEADERS operation on line 3600 uses the same HEADINGS$, WIDTHS, and FORMS$ as the previously constructed LISTs. Line 3700 sets CURFLD to be on the sixth cell of the GRID (this is discussed in the next section). Line 3800 populates the GRID. The user may change the contents of the cells. &lt;br /&gt;
&lt;br /&gt;
 03400 print fields &amp;quot;23,1,C 50&amp;quot;&amp;amp;nbsp;: &amp;quot;Press enter to continue&amp;quot;: let KSTAT$(1)&lt;br /&gt;
 03500 print NEWPAGE&lt;br /&gt;
 03600 print fields &amp;quot;1,1,grid 8/60,headers&amp;quot;: (MAT HEADINGS$,MAT WIDTHS,MAT FORMS$)&lt;br /&gt;
 03700 let CURFLD (1,6)&lt;br /&gt;
 03800 print fields &amp;quot;1,1,grid 8/60,=&amp;quot;: (MAT NAMES$,MAT CITIES$)&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6.jpg]] &lt;br /&gt;
&lt;br /&gt;
The seventh part of the program demonstrates the use of read types CNT, CELL, and SUB and selection type CHG. Line 3900 counts the number of changed cells and inputs that number into variable CELLS. Line 4000 redimensions the matrix SUBSCR to the number of changed cells. Line 4100 inputs the changed cells into SUBSCR. Line 4200 redimensions the matrix DATA$.  Line 4300 inputs the subscripts of the changed cells into matrix DATA$. Line 4400 prints DATA$. &lt;br /&gt;
&lt;br /&gt;
 03900 input fields &amp;quot;1,1,grid 8/60,cnt,chg&amp;quot;: CELLS&lt;br /&gt;
 04000 mat SUBSCR(CELLS)&lt;br /&gt;
 04100 input fields &amp;quot;1,1,grid 8/60,sub,chg,nowait&amp;quot;: MAT SUBSCR&lt;br /&gt;
 04200 mat DATA$(CELLS)&lt;br /&gt;
 04300 input fields &amp;quot;1,1,grid 8/60,cell,chg,nowait&amp;quot;: MAT DATA$&lt;br /&gt;
 04400 print MAT DATA$&lt;br /&gt;
&lt;br /&gt;
===== Output  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output6b.jpg]]&lt;br /&gt;
&lt;br /&gt;
===== Output of line 04400  =====&lt;br /&gt;
&lt;br /&gt;
[[Image:Output7.jpg]]&lt;br /&gt;
&lt;br /&gt;
=== Displaying a List or Grid (Output Operations) ===&lt;br /&gt;
[[file:Grid2.png|900px]]&lt;br /&gt;
&lt;br /&gt;
To display a listview or a grid, you must set the headers first, using a special PRINT FIELDS operation.&lt;br /&gt;
&lt;br /&gt;
====HEADERS====&lt;br /&gt;
The HEADERS operation sets the column headings and widths. The corresponding input/output list value must be a parenthesized group of three arrays, for example: &lt;br /&gt;
&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$)&lt;br /&gt;
      - or -&lt;br /&gt;
 00250 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS,MAT FIELD_FORMS$)&lt;br /&gt;
&lt;br /&gt;
The [hdrs] notation refers to an optional CONFIG ATTRIBUTE [HDRS] specification for setting the appearance of the header row. In this case the [] brackets are required. &lt;br /&gt;
&lt;br /&gt;
If a function key value (e.g. 1520) is given then when the control is not active, it may be clicked to trigger the specified interrupt similar to any other hot control. A function key interrupt is also triggered by double clicking during an Input operation. &lt;br /&gt;
&lt;br /&gt;
MAT HEADINGS$ Contains the column titles that will be displayed at the top of each column. The font, color and shading of these titles can be set through the [HDRS] or similar substitution attribute. &lt;br /&gt;
&lt;br /&gt;
MAT WIDTHS specifies DISPLAYED Column Widths and is expressed as the number of character positions occupied by each column. Scrollbars are provided as needed to honor overall control size specified in the FIELDS specification. &lt;br /&gt;
&lt;br /&gt;
For example, if four columns are specified and the widths are given as 10, 15, 10 and 5, then the 2D control occupies 40 character positions (plus a little for the column separators) irrespective of the number of columns specified for the display area. If needed, scroll bars are used to display wider controls within the displayed area. &lt;br /&gt;
&lt;br /&gt;
Displayed widths of zero characters are allowed. This enables the use of hidden columns for storing things like record numbers and record keys. &lt;br /&gt;
&lt;br /&gt;
MAT FIELD_FORMS$ provides the BR FORM for each column. e.g. C 15 stipulates a maximum field capacity of 15. The actual displayed length is a function of the grid size and the column relative width. &lt;br /&gt;
&lt;br /&gt;
The field form array elements may also include a comma followed by leading field attributes (e.g. color) pertaining to the column.  &lt;br /&gt;
&lt;br /&gt;
The number of columns must be set with HEADERS prior to Populating a control (loading data into it).&lt;br /&gt;
&lt;br /&gt;
====MASKing====&lt;br /&gt;
&lt;br /&gt;
As of 4.3, LIST and GRID support the MASK operation, both in READs and Populating, as seen in this section. For example:&lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;row,col,LIST rows/cols,MASK&amp;quot; :  mask_array&lt;br /&gt;
&lt;br /&gt;
This restricts the rows (for both LIST and GRID) previously displayed to those corresponding to a “true” value in mask_array. A true value is represented in a numeric array as a value greater than zero. Negative values are not allowed in mask arrays. A string mask array may also be used with “T” and “F” values. The MASK stays in effect until 1) a new MASK is specified or 2) the contents of the control are changed with PRINT ( &amp;lt;nowiki&amp;gt;=, +, -,&amp;lt;/nowiki&amp;gt; see primary flags below).  Also, the mask array affects only the user presentation, not the result set. &lt;br /&gt;
&lt;br /&gt;
====Populating====&lt;br /&gt;
&lt;br /&gt;
The populate operation loads data into the control. In the following example four columns are loaded: &lt;br /&gt;
&lt;br /&gt;
 03010 PRINT FIELDS &amp;quot;10,20,LIST 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE, MAT WEIGHT)&lt;br /&gt;
      - or -&lt;br /&gt;
 03020 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
In this example, the fourth element of the HEADERS FIELD_FORM$ array (above) could specify rounding of WEIGHT to two decimal places. If an fkey value is specified, then double-clicking (or single clicking if S is specified) a cell will generate the specified fkey interrupt. &lt;br /&gt;
&lt;br /&gt;
Permissible leading attribute values are:&lt;br /&gt;
&lt;br /&gt;
===== Primary Flags  =====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;=&#039;&#039;&#039; &lt;br /&gt;
| Replace any previous data&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;+&#039;&#039;&#039; &lt;br /&gt;
| Add to any previously populated data (this allows loading in chunks)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;-&#039;&#039;&#039; &lt;br /&gt;
| Insert data ahead of previously populated data (4.16+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Secondary Flags  ====&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;R&#039;&#039;&#039; &lt;br /&gt;
| Load one row at a time (the default - use grouped IO parens)&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;C&#039;&#039;&#039; &lt;br /&gt;
| Load one column at a time - This is for loading multiple columns of the same data type&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;L&#039;&#039;&#039; &lt;br /&gt;
| Provide the FKEY (see INPUT below) or Enter interrupt if the user presses up arrow or page up in the first field, or down arrow or page down in the last field. Note that this is not specified in the individual field leading attributes.&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;S&#039;&#039;&#039; &lt;br /&gt;
| Single click to activate an Enter or FKEY event (otherwise a double click is required) (4.17+)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Note that the following example will NOT work- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=C&amp;quot;: MAT NAME$,MAT CITY$,MAT AGE,MAT WEIGHT&lt;br /&gt;
&lt;br /&gt;
The reason is that only MAT NAME$ will reach the list. MAT CITY$, MAT AGE and MAT WEIGHT will be associated with subsequent fields. Multiple arrays provided to a single control must be grouped with parentheses. &lt;br /&gt;
&lt;br /&gt;
Populating a two-dimensional object by row with grouped IO means NAME$(1) will go into (1,1), AGE(1) will go into (1,2) and WGT(1) will go into (1,3) and so on. If a single array (MAT DATA$) is specified instead of a group, MAT DATA$ is applied horizontally instead of vertically. So DATA$(1) - DATA$(3) will be the first row and DATA$(4) - DATA$(6) will be the next row and so on. &lt;br /&gt;
&lt;br /&gt;
Populating a two dimensional grid by column with an array named DATA$ means that DATA$(1) goes in (1,1) and DATA$(2) goes in (2,1) and DATA$(3) goes in (3,1). Therefore the first however many values of DATA refer to the first column and the second however many values of DATA refer to the second column. So if there are 3 columns and UDIM(DATA$) = 90 then DATA$(1)-DATA$(30) is the first column, and DATA$(31) - DATA$(60) is the second column and DATA$(61) - DATA$(90) is the last column. &lt;br /&gt;
&lt;br /&gt;
For example, using the following information, each example demonstrates how the grid will be filled:&lt;br /&gt;
&lt;br /&gt;
MAT NAME$ = George, Peter, Tom &lt;br /&gt;
&lt;br /&gt;
MAT CITY$ = Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
MAT AGE$ = 42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
MAT WEIGHT$ = 180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Peter, Detroit, 23, 212 &lt;br /&gt;
&lt;br /&gt;
Tom, Denver, 35, 193 &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,GRID 10/80, =C&amp;quot;: (MAT NAME$, MAT CITY$,MAT AGE$, MAT WEIGHT$)&lt;br /&gt;
&lt;br /&gt;
Dallas, Detroit, Denver &lt;br /&gt;
&lt;br /&gt;
42, 23, 35 &lt;br /&gt;
&lt;br /&gt;
180, 212, 193 &lt;br /&gt;
&lt;br /&gt;
As you can see using C with grouped arrays is counter-intuitive and doesn&#039;t fit well. C is most useful with a single array that should be loaded vertically down several columns. &lt;br /&gt;
&lt;br /&gt;
===== Grid Validation  =====&lt;br /&gt;
&lt;br /&gt;
GRIDs are now validated as each cell is exited instead of when control is passed to the BR program after all data is entered.&lt;br /&gt;
&lt;br /&gt;
===== Color and Font changes in Cells  =====&lt;br /&gt;
&lt;br /&gt;
The attributes that determine font, color and style in each cell can be set for an entire column by including these parameters in the heading FORM array. Individual cells can then be changed using a PRINT statement. &lt;br /&gt;
&lt;br /&gt;
The format of the print statement is &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT #WINNO, fields &amp;quot;2,2,LIST 10/60,ATTR&amp;quot;:(mat start, mat end, mat attribute$)&lt;br /&gt;
&lt;br /&gt;
With the following parameter descriptions: &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat start&#039;&#039;&#039; &lt;br /&gt;
| contains the cell number(s) where the attribute chain begins,&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat end&#039;&#039;&#039; &lt;br /&gt;
| contains the last cell number where the attribute applies&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| width=&amp;quot;10%&amp;quot; | &#039;&#039;&#039;mat attribute$&#039;&#039;&#039; &lt;br /&gt;
| contains the attribute specification that should be applied to the cell range(s) specified.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If the 2d control is a GRID, then mat Start and mat End refer to starting and ending Cell Numbers. If the 2d control is a listview, then mat Start and mat End refer to starting and ending Row Numbers. &lt;br /&gt;
&lt;br /&gt;
The attributes specified for any COLUMN can be overridden on a cell basis by specifying the starting cell number, ending cell number, and the overriding attributes in three arrays that are printed to the grid window with the same grid specificatoins and the key word &amp;quot;ATTR&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
 02420 PRINT #BLISTWIN,FIELDS BLISTSPEC$&amp;amp;amp;&amp;quot;,ATTR&amp;quot;: (MAT BROWS,MAT BROWE,MAT BATT$)&lt;br /&gt;
&lt;br /&gt;
In the above example, BLISTWIN is the window number, BLISTSPEC$ is the grid specification (&amp;quot;GRID 10/40&amp;quot; for example), BROWS is an array holding the starting cell number, BROWE is an array holding the ending cell number, and BATT$ is an array holding the overriding attributes. In a list the attributes of the first cell in the row controls the appearance of the entire row.&lt;br /&gt;
&lt;br /&gt;
 01500 PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,ATTR&amp;quot;: (mat start, mat end, mat attr$)&lt;br /&gt;
&lt;br /&gt;
The above example overrides the attributes of a range of cells/rows for a GRID/LIST display. This allows you to shade or otherwise alter the display of a range of cells / rows in a 2D control.&lt;br /&gt;
&lt;br /&gt;
====Aggregate Sorting====&lt;br /&gt;
BR supports aggregated sorting for LISTs and GRIDs. This means when clicking &lt;br /&gt;
on various column headings or programmatically sorting columns, fields of &lt;br /&gt;
equal values retain their previous order within their new groupings (4.2).&lt;br /&gt;
&lt;br /&gt;
==== Numeric Column Sorting  ====&lt;br /&gt;
&lt;br /&gt;
2D controls now facilitate numeric column sorting. This works well in conjunction with the new [[Date (Format Specification)|DATE]] field format (see release notes [[4.16]]) where the data is stored as day of century, but is displayed as a formatted date. It also works with all numeric columns.&lt;br /&gt;
&lt;br /&gt;
If a listview columns form spec if given as DATE(mm/dd/ccyy), for example, then any time that column is sorted, either as a result of the user clicking on the column heading, or the program giving the sort command (shown below), the dates are properly sorted even though they&#039;re displayed as mm/dd/ccyy. For this to work, the data has to be given in the julian days format, see the [[DAYS]] function for more information.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In versions 4.2 and higher the syntax for sorting a listview is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
&lt;br /&gt;
To sort in reverse order, sort the column twice:&lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column number }&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { same column number }&lt;br /&gt;
&lt;br /&gt;
This has been extended in version 4.3 to allow a numeric array instead of a scalar. If an array is given, it is assumed to be in the same format that SORT_ORDER returns. The new format is: &lt;br /&gt;
&lt;br /&gt;
    PRINT FIELDS &amp;quot;nn,nn,GRID 10/40,SORT&amp;quot;: { column-number | numeric-array }&lt;br /&gt;
&lt;br /&gt;
Where the numeric-array has one element for each column left to right. BR will resort the columns in the requested order. &lt;br /&gt;
&lt;br /&gt;
The numeric array values indicating the order of column sorting to be performed do not need to exactly match the standard format. e.g Fractions are allowed, the values can fall within any range, and there does not need to be trailing zero elements for unsorted columns.&lt;br /&gt;
&lt;br /&gt;
==== NOSORT for Columns  ====&lt;br /&gt;
As of 4.2, the &#039;&#039;&#039;NoSort&#039;&#039;&#039; parameter is used to prevent users from sorting columns of a Grid or List. &lt;br /&gt;
&lt;br /&gt;
For the statement: &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;10,20,GRID 10/80,HEADERS,[hdrs],1520&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FIELD_FORMS$) &lt;br /&gt;
&lt;br /&gt;
The field attribute &amp;quot;^nosort&amp;quot; appearing in the MAT FIELD_FORM$ prevents the sorting of a grid or listview in response to the user clicking on the corresponding column header. This does not prevent programs from sorting on those columns.&lt;br /&gt;
&lt;br /&gt;
==== Range Input  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are used in versions 4.3 and higher. In these examples BR will redimension the receiving arrays as needed: &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, CELL, RANGE&amp;quot; :&lt;br /&gt;
             start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This reads the specified range of cells. BR redimensions MAT Data$ as needed. Note that CELL may now be used with LIST. Previously, LISTs were only addressable by row. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot; :&lt;br /&gt;
             (start:=7), (end:=11), ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads the cells in rows 7 through 11. The receiving arrays are re-dimensioned as appropriate. &lt;br /&gt;
&lt;br /&gt;
 INPUT FIELDS &amp;quot;row,col,GRID rows/cols, ROW, RANGE&amp;quot;:&lt;br /&gt;
             MAT start, MAT end, ( MAT Data1$, MAT Data2$, MAT Data3 )&lt;br /&gt;
&lt;br /&gt;
This reads one or more ranges of rows. &lt;br /&gt;
&lt;br /&gt;
A more detailed example of this is: &lt;br /&gt;
&lt;br /&gt;
 100 ! create and populate a LIST control &lt;br /&gt;
 200 MAT START(3) : MAT END(3)&lt;br /&gt;
 210 READ MAT START, MAT END&lt;br /&gt;
 220 DATA 7,21,33&lt;br /&gt;
 230 DATA 11,21,38&lt;br /&gt;
 240 INPUT FIELDS &amp;quot;row,col,LIST rows/cols, ROW, RANGE&amp;quot;&amp;amp;nbsp;: MAT START,&lt;br /&gt;
            MAT END, ( MAT Array1$, MAT Array2, MAT Array3$ )&lt;br /&gt;
&lt;br /&gt;
This reads 12 rows of data ( row 7-11, row 21 and rows 33-38 ). &lt;br /&gt;
&lt;br /&gt;
==== Range Output  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions [[4.3]] and higher. By default, RANGE output refers to rows. The special keyword CELL_RANGE is used to denote the specification of cell ranges. Additionally, the use of scalars versus arrays for start and end values determines important characteristics of the output process. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; Using Scalars For Range Specification &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,GRID 10/75, RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
This replaces the values in rows numbered &#039;start&#039; through &#039;end&#039; with the data in MAT Data$. The size of MAT Data$ must be a multiple of the number of columns in the GRID or an error is generated. &lt;br /&gt;
&lt;br /&gt;
 PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$,                                   MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
This replaces the values in ROWs numbered &#039;start&#039; through &#039;end&#039; with the data from MATs NAME$, CITY$, AGE and WEIGHT. The data arrays must all be dimensioned the same. &lt;br /&gt;
&lt;br /&gt;
==== Insertion and Deletion with RANGE ====&lt;br /&gt;
&lt;br /&gt;
The number of rows being output do not need to match the number of rows being replaced. To delete a range of rows, output one or more grouped arrays with zero elements. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. Using the following statement, various scenarios are described. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays have been DIMed to nine elements&lt;br /&gt;
 Result- Nine rows replace five, and the total content of the control is expanded by 4 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=11, and the arrays are DIMed to zero elements&lt;br /&gt;
 Result- Five rows are deleted, and the total size of the control is reduced by 5 rows.&lt;br /&gt;
&lt;br /&gt;
 start=7, end=0 (anything less than 7), and the arrays are DIMed to support three rows&lt;br /&gt;
 Result- Three rows are inserted ahead of row seven and the total content of the control is expanded by three rows&lt;br /&gt;
&lt;br /&gt;
 start=5000, end={any value}, the control only has 482 rows, and the source arrays are DIMed to support eleven rows&lt;br /&gt;
 Result- Eleven rows are appended to the end of the control and become rows 483 through 493.&lt;br /&gt;
&lt;br /&gt;
==== Outputting Ranges of Cells  ====&lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher. &lt;br /&gt;
&lt;br /&gt;
Ranges of cells may be output in conjunction with the CELL_RANGE keyword. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,LIST 10/75, CELL_RANGE&amp;quot;: start, end, (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
                                - or -&lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, end, MAT Data$&lt;br /&gt;
&lt;br /&gt;
In this case, start and end specify cells instead of rows. If insertion or deletion is indicated by dimensioning the data arrays to greater or fewer elements than are being replaced, then the data must be a multiple of the number of columns. Insertion and deletion is only valid in terms of rows, even when cell subscripts are used to specify ranges. In such cases, if the cell subscripts are not on row boundaries, an error is generated. &lt;br /&gt;
&lt;br /&gt;
  PRINT FIELDS &amp;quot;7,8,GRID 10/75, CELL_RANGE&amp;quot;: start, start, Data$&lt;br /&gt;
&lt;br /&gt;
In this example, the value in one cell is replaced with the content of a scalar.&lt;br /&gt;
&lt;br /&gt;
==== Using Arrays For Range Specification  ====&lt;br /&gt;
&lt;br /&gt;
If the start and end specifications are array denoting multiple ranges, there must be a one to one correspondence between the number of rows specified and those in the data. This method implies replacement only and insertion or deletion is not allowed. &lt;br /&gt;
&lt;br /&gt;
The data flow that this feature was designed to support is one where the user is presented with a LIST or GRID where multiple rows have been either selected or changed before returning control to the program and the program is responding by updating something on those rows. &lt;br /&gt;
&lt;br /&gt;
The program begins by presenting a 2D control to the user and reading the the control with type ROWSUB or SUB. Type SUB only works for GRIDs where all colmns have the same data type. Of course the subscripts are read into a numeric array which BR redimensions as appropriate. Then the program reads the changed or selected data with NOWAIT. (This resets the CHG flags in the control.) The program then changes either row (ROWSUB) or cell (SUB) data and outputs the results using the subscript array as both the start and end specification. Other scenarios are possible but this is the primary intended use. &lt;br /&gt;
&lt;br /&gt;
The following examples are valid for versions 4.3 and higher: &lt;br /&gt;
&lt;br /&gt;
  100 ! create and populate a GRID --&lt;br /&gt;
  200 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROWSUB,CHG&amp;quot;: MAT Rowsubs&lt;br /&gt;
         (Reading subscripts does not reset the CHG flags in the control.)&lt;br /&gt;
  210 INPUT FIELDS &amp;quot;row,col,GRID rows/cols,ROW,CHG,NOWAIT&amp;quot;: ( MAT Data1$,&lt;br /&gt;
        MAT Data2, MAT Data3$ )&lt;br /&gt;
          BR redimensions the receiving arrays as needed.&lt;br /&gt;
         (Reading the data also resets the CHG flags in the control.)&lt;br /&gt;
&lt;br /&gt;
  220 ! process the changed rows now present in the data arrays --&lt;br /&gt;
  300 PRINT FIELDS &amp;quot;row,col,GRID rows/cols,RANGE&amp;quot;: MAT Rowsubs,&lt;br /&gt;
                   MAT Rowsubs, ( MAT Data1$, MAT Data2, MAT Data3$ )&lt;br /&gt;
&lt;br /&gt;
This outputs the updated rows. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Grid and List BR_VB Similarities  ====&lt;br /&gt;
Before introducing grids and lists in BR, similar effects could be achieved using BR_VB to work with Visual Basic. If you were familiar with BR_VB, the following notes may be of interest:&lt;br /&gt;
&lt;br /&gt;
Grid and list controls work like the BR_VB interface except headers now specify the form of each column and a new input type has been added: &lt;br /&gt;
&lt;br /&gt;
A multi column LISTview- &lt;br /&gt;
&lt;br /&gt;
 00100 PRINT FIELDS &amp;quot;10,20,LIST 10/80,HEADERS[,hdrs][,fkey]&amp;quot;: (MAT HEADINGS$, MAT WIDTHS, MAT FORMS$) &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note-&#039;&#039;&#039; Widths are expressed in character positions, not percentages like they are in BR_VB. &lt;br /&gt;
&lt;br /&gt;
The populate operation- &lt;br /&gt;
&lt;br /&gt;
 00200 PRINT FIELDS &amp;quot;10,20,LIST 10/80,=&amp;quot;: (MAT NAME$, MAT CITY$, MAT AGE, MAT WEIGHT)&lt;br /&gt;
&lt;br /&gt;
Read-Ctl type: CNT (in place of CELLROWSUB) returns a single numeric value which is the number of subscripts available to read. In the case of grids, this is the number of cells. In the case of LISTviews, this is the number of rows. &lt;br /&gt;
&lt;br /&gt;
 00110 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROWCNT,SEL&amp;quot;: avail_rows&amp;amp;nbsp;! # of selected rows&lt;br /&gt;
 00120 MAT DATA$(3 * avail_rows)         &amp;amp;nbsp;! redimension.. 3 cols x selected rows&lt;br /&gt;
 00130 INPUT FIELDS &amp;quot;10,20,LIST 10/80,ROW,SEL,NOWAIT&amp;quot;: MAT DATA$&amp;amp;nbsp;! read rows&lt;br /&gt;
&lt;br /&gt;
====See Also: ====&lt;br /&gt;
* [[Grids Tutorial]]&lt;br /&gt;
* [[0886]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
[[Category:Widget]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11438</id>
		<title>DATABASE</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11438"/>
		<updated>2023-07-31T02:29:59Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* DSN */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In version 4.3 and higher [[CONFIG]] DATABASE may use one of 3 methods for connecting to SQL Sources:&lt;br /&gt;
*DSN&lt;br /&gt;
*CONNECTSTRING&lt;br /&gt;
*ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
===DSN===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  DSN=&amp;lt;dsn-ref&amp;gt;  [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
The ? indicates prompt&lt;br /&gt;
&lt;br /&gt;
Example: &lt;br /&gt;
 EXECUTE &#039;CONFIG DATABASE CLS_Data DSN=&amp;quot;MyData&amp;quot; , PASSWORDD= &amp;lt;encrypted-password&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;[[Encrypt$|Encrypted passwords]]&#039;&#039; are expressed  as hexadecimal values. BR will &#039;&#039;[[UnHex$|Unhex]]&#039;&#039; the value  and then &#039;&#039;[[Decrypt$|Decrypt]]&#039;&#039; it before presenting it to SQL Server.&lt;br /&gt;
&lt;br /&gt;
===CONNECTSTRING (DSN Less)===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  CONNECTSTRING=&amp;quot;&amp;lt;Driver&amp;gt;={Microsoft Access Driver (*.mdb)} DBQ=C:\inetpub\wwwroot\BegASP\Chapter.14\Contact.mdb&amp;quot; [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sample Connection Strings:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w SQL Login:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;SERVER=server;Initial Catalog=database;UID=username;PWD=password&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;password&amp;quot; is the [SQL Server Password].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server with Windows Authentication:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;Initial Catalog=database;Persist Security Info=True;MultipleBC_TableResultSets=True; Database=database;SERVER=server&amp;quot;   User=LOGIN_NAME  Password=BR_PASSWORD&lt;br /&gt;
&lt;br /&gt;
Notice that the connection string, placed within quotes, is separate from the &#039;&#039;&#039;User=&#039;&#039;&#039; and &#039;&#039;&#039;Password=&#039;&#039;&#039; parameters. If the additional parameters are specified, BR will augment the connection string with &#039;&#039;&#039;UID=&#039;&#039;&#039; and &#039;&#039;&#039;PWD=&#039;&#039;&#039; parameters. Do not confuse connection string valid parameters with BR augmentation parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database]. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
BR_PASSWORD will use the users Active Directory password to connect to the SQL Server.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SQL Server&amp;quot; is one of several choices for SQL Server, another choice would be SQL Server Native Client 11.0.&lt;br /&gt;
&lt;br /&gt;
===ODBC-MANAGER===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt; ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
Using ODBC Manager as the parameter will allow the end user to select the desired data source.&lt;br /&gt;
&lt;br /&gt;
The following is a sample program that will use the ODBC-MANAGER to identify the proper connection string.&lt;br /&gt;
* Note: This will not work in Client Server because the operation is performed on the server and this process depends on the user&#039;s settings.&lt;br /&gt;
&lt;br /&gt;
 00010   PRINT Newpage&lt;br /&gt;
 00011   IF Env$(&amp;quot;br_model&amp;quot;)=&amp;quot;CLIENT_SERVER&amp;quot; THEN PRINT &amp;quot;Warning, you cannot connect to ODBC-MANAGER in Client Server Mode&amp;quot;&lt;br /&gt;
 00020   EXECUTE &amp;quot;Config Database Test_DB ODBC-MANAGER&amp;quot; ERROR CONNECT_ERROR&lt;br /&gt;
 00030   PRINT &amp;quot;Connection String is:&amp;quot; !:&lt;br /&gt;
        PRINT Env$(&amp;quot;STATUS.DATABASE.[TEST_DB].CONNECTSTRING&amp;quot;)&lt;br /&gt;
 00099   STOP &lt;br /&gt;
 00100 CONNECT_ERROR: ! &lt;br /&gt;
 00110   PRINT &amp;quot;Error Connecting - Err:&amp;quot;;Err;&amp;quot; Line:&amp;quot;;Line;&amp;quot; Syserr:&amp;quot;;Syserr$&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:All Parameters]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11437</id>
		<title>DATABASE</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11437"/>
		<updated>2023-07-31T02:19:36Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* DSN */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In version 4.3 and higher [[CONFIG]] DATABASE may use one of 3 methods for connecting to SQL Sources:&lt;br /&gt;
*DSN&lt;br /&gt;
*CONNECTSTRING&lt;br /&gt;
*ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
===DSN===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  DSN=&amp;lt;dsn-ref&amp;gt;  [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
The ? indicates prompt&lt;br /&gt;
&lt;br /&gt;
Example: &lt;br /&gt;
 EXECUTE &#039;CONFIG DATABASE CLS_Data DSN=&amp;quot;MyData&amp;quot; , PASSWORDD= &amp;lt;encrypted-password&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
[[Encrypt$|Encrypted passwords]] are expressed  as hexadecimal values.&lt;br /&gt;
&lt;br /&gt;
===CONNECTSTRING (DSN Less)===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  CONNECTSTRING=&amp;quot;&amp;lt;Driver&amp;gt;={Microsoft Access Driver (*.mdb)} DBQ=C:\inetpub\wwwroot\BegASP\Chapter.14\Contact.mdb&amp;quot; [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sample Connection Strings:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w SQL Login:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;SERVER=server;Initial Catalog=database;UID=username;PWD=password&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;password&amp;quot; is the [SQL Server Password].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server with Windows Authentication:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;Initial Catalog=database;Persist Security Info=True;MultipleBC_TableResultSets=True; Database=database;SERVER=server&amp;quot;   User=LOGIN_NAME  Password=BR_PASSWORD&lt;br /&gt;
&lt;br /&gt;
Notice that the connection string, placed within quotes, is separate from the &#039;&#039;&#039;User=&#039;&#039;&#039; and &#039;&#039;&#039;Password=&#039;&#039;&#039; parameters. If the additional parameters are specified, BR will augment the connection string with &#039;&#039;&#039;UID=&#039;&#039;&#039; and &#039;&#039;&#039;PWD=&#039;&#039;&#039; parameters. Do not confuse connection string valid parameters with BR augmentation parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database]. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
BR_PASSWORD will use the users Active Directory password to connect to the SQL Server.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SQL Server&amp;quot; is one of several choices for SQL Server, another choice would be SQL Server Native Client 11.0.&lt;br /&gt;
&lt;br /&gt;
===ODBC-MANAGER===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt; ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
Using ODBC Manager as the parameter will allow the end user to select the desired data source.&lt;br /&gt;
&lt;br /&gt;
The following is a sample program that will use the ODBC-MANAGER to identify the proper connection string.&lt;br /&gt;
* Note: This will not work in Client Server because the operation is performed on the server and this process depends on the user&#039;s settings.&lt;br /&gt;
&lt;br /&gt;
 00010   PRINT Newpage&lt;br /&gt;
 00011   IF Env$(&amp;quot;br_model&amp;quot;)=&amp;quot;CLIENT_SERVER&amp;quot; THEN PRINT &amp;quot;Warning, you cannot connect to ODBC-MANAGER in Client Server Mode&amp;quot;&lt;br /&gt;
 00020   EXECUTE &amp;quot;Config Database Test_DB ODBC-MANAGER&amp;quot; ERROR CONNECT_ERROR&lt;br /&gt;
 00030   PRINT &amp;quot;Connection String is:&amp;quot; !:&lt;br /&gt;
        PRINT Env$(&amp;quot;STATUS.DATABASE.[TEST_DB].CONNECTSTRING&amp;quot;)&lt;br /&gt;
 00099   STOP &lt;br /&gt;
 00100 CONNECT_ERROR: ! &lt;br /&gt;
 00110   PRINT &amp;quot;Error Connecting - Err:&amp;quot;;Err;&amp;quot; Line:&amp;quot;;Line;&amp;quot; Syserr:&amp;quot;;Syserr$&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:All Parameters]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11436</id>
		<title>DATABASE</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11436"/>
		<updated>2023-07-31T02:11:32Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* CONNECTSTRING (DSN Less) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In version 4.3 and higher [[CONFIG]] DATABASE may use one of 3 methods for connecting to SQL Sources:&lt;br /&gt;
*DSN&lt;br /&gt;
*CONNECTSTRING&lt;br /&gt;
*ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
===DSN===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  DSN=&amp;lt;dsn-ref&amp;gt;  [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
The ? indicates prompt&lt;br /&gt;
&lt;br /&gt;
Example: &lt;br /&gt;
 EXECUTE &#039;CONFIG DATABASE CLS_Data DSN=&amp;quot;MyData&amp;quot; , PASSWORDD= &amp;lt;encrypted-password&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
===CONNECTSTRING (DSN Less)===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  CONNECTSTRING=&amp;quot;&amp;lt;Driver&amp;gt;={Microsoft Access Driver (*.mdb)} DBQ=C:\inetpub\wwwroot\BegASP\Chapter.14\Contact.mdb&amp;quot; [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sample Connection Strings:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w SQL Login:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;SERVER=server;Initial Catalog=database;UID=username;PWD=password&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;password&amp;quot; is the [SQL Server Password].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server with Windows Authentication:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;Initial Catalog=database;Persist Security Info=True;MultipleBC_TableResultSets=True; Database=database;SERVER=server&amp;quot;   User=LOGIN_NAME  Password=BR_PASSWORD&lt;br /&gt;
&lt;br /&gt;
Notice that the connection string, placed within quotes, is separate from the &#039;&#039;&#039;User=&#039;&#039;&#039; and &#039;&#039;&#039;Password=&#039;&#039;&#039; parameters. If the additional parameters are specified, BR will augment the connection string with &#039;&#039;&#039;UID=&#039;&#039;&#039; and &#039;&#039;&#039;PWD=&#039;&#039;&#039; parameters. Do not confuse connection string valid parameters with BR augmentation parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database]. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
BR_PASSWORD will use the users Active Directory password to connect to the SQL Server.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SQL Server&amp;quot; is one of several choices for SQL Server, another choice would be SQL Server Native Client 11.0.&lt;br /&gt;
&lt;br /&gt;
===ODBC-MANAGER===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt; ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
Using ODBC Manager as the parameter will allow the end user to select the desired data source.&lt;br /&gt;
&lt;br /&gt;
The following is a sample program that will use the ODBC-MANAGER to identify the proper connection string.&lt;br /&gt;
* Note: This will not work in Client Server because the operation is performed on the server and this process depends on the user&#039;s settings.&lt;br /&gt;
&lt;br /&gt;
 00010   PRINT Newpage&lt;br /&gt;
 00011   IF Env$(&amp;quot;br_model&amp;quot;)=&amp;quot;CLIENT_SERVER&amp;quot; THEN PRINT &amp;quot;Warning, you cannot connect to ODBC-MANAGER in Client Server Mode&amp;quot;&lt;br /&gt;
 00020   EXECUTE &amp;quot;Config Database Test_DB ODBC-MANAGER&amp;quot; ERROR CONNECT_ERROR&lt;br /&gt;
 00030   PRINT &amp;quot;Connection String is:&amp;quot; !:&lt;br /&gt;
        PRINT Env$(&amp;quot;STATUS.DATABASE.[TEST_DB].CONNECTSTRING&amp;quot;)&lt;br /&gt;
 00099   STOP &lt;br /&gt;
 00100 CONNECT_ERROR: ! &lt;br /&gt;
 00110   PRINT &amp;quot;Error Connecting - Err:&amp;quot;;Err;&amp;quot; Line:&amp;quot;;Line;&amp;quot; Syserr:&amp;quot;;Syserr$&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:All Parameters]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11435</id>
		<title>DATABASE</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11435"/>
		<updated>2023-07-31T02:09:56Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* CONNECTSTRING (DSN Less) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In version 4.3 and higher [[CONFIG]] DATABASE may use one of 3 methods for connecting to SQL Sources:&lt;br /&gt;
*DSN&lt;br /&gt;
*CONNECTSTRING&lt;br /&gt;
*ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
===DSN===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  DSN=&amp;lt;dsn-ref&amp;gt;  [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
The ? indicates prompt&lt;br /&gt;
&lt;br /&gt;
Example: &lt;br /&gt;
 EXECUTE &#039;CONFIG DATABASE CLS_Data DSN=&amp;quot;MyData&amp;quot; , PASSWORDD= &amp;lt;encrypted-password&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
===CONNECTSTRING (DSN Less)===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  CONNECTSTRING=&amp;quot;&amp;lt;Driver&amp;gt;={Microsoft Access Driver (*.mdb)} DBQ=C:\inetpub\wwwroot\BegASP\Chapter.14\Contact.mdb&amp;quot; [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sample Connection Strings:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w SQL Login:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;SERVER=server;Initial Catalog=database;UID=username;PWD=password&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;password&amp;quot; is the [SQL Server Password].&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server with Windows Authentication:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;Initial Catalog=database;Persist Security Info=True;MultipleBC_TableResultSets=True; Database=database;SERVER=server&amp;quot;   User=LOGIN_NAME  Password=BR_PASSWORD&lt;br /&gt;
&lt;br /&gt;
Notice that the connection string, placed within quotes, is separate from the &#039;&#039;&#039;User=&#039;&#039;&#039; and &#039;&#039;&#039;Password=&#039;&#039;&#039; parameters. If the additional parameters are specified, BR will augment the connection string with &#039;&#039;&#039;UID=&#039;&#039;&#039; and &#039;&#039;&#039;PWD=&#039;&#039;&#039; parameters. Do not confuse connection string valid parameters with BR augmentation parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database]. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
BR_PASSWORD will use the users Active Directory password to connect to the SQL Server.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SQL Server&amp;quot; is one of several choices for SQL Server, another choice would be SQL Server Native Client 11.0.&lt;br /&gt;
&lt;br /&gt;
===ODBC-MANAGER===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt; ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
Using ODBC Manager as the parameter will allow the end user to select the desired data source.&lt;br /&gt;
&lt;br /&gt;
The following is a sample program that will use the ODBC-MANAGER to identify the proper connection string.&lt;br /&gt;
* Note: This will not work in Client Server because the operation is performed on the server and this process depends on the user&#039;s settings.&lt;br /&gt;
&lt;br /&gt;
 00010   PRINT Newpage&lt;br /&gt;
 00011   IF Env$(&amp;quot;br_model&amp;quot;)=&amp;quot;CLIENT_SERVER&amp;quot; THEN PRINT &amp;quot;Warning, you cannot connect to ODBC-MANAGER in Client Server Mode&amp;quot;&lt;br /&gt;
 00020   EXECUTE &amp;quot;Config Database Test_DB ODBC-MANAGER&amp;quot; ERROR CONNECT_ERROR&lt;br /&gt;
 00030   PRINT &amp;quot;Connection String is:&amp;quot; !:&lt;br /&gt;
        PRINT Env$(&amp;quot;STATUS.DATABASE.[TEST_DB].CONNECTSTRING&amp;quot;)&lt;br /&gt;
 00099   STOP &lt;br /&gt;
 00100 CONNECT_ERROR: ! &lt;br /&gt;
 00110   PRINT &amp;quot;Error Connecting - Err:&amp;quot;;Err;&amp;quot; Line:&amp;quot;;Line;&amp;quot; Syserr:&amp;quot;;Syserr$&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:All Parameters]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11434</id>
		<title>DATABASE</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11434"/>
		<updated>2023-07-31T01:30:56Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* ODBC-MANAGER */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In version 4.3 and higher [[CONFIG]] DATABASE may use one of 3 methods for connecting to SQL Sources:&lt;br /&gt;
*DSN&lt;br /&gt;
*CONNECTSTRING&lt;br /&gt;
*ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
===DSN===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  DSN=&amp;lt;dsn-ref&amp;gt;  [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
The ? indicates prompt&lt;br /&gt;
&lt;br /&gt;
Example: &lt;br /&gt;
 EXECUTE &#039;CONFIG DATABASE CLS_Data DSN=&amp;quot;MyData&amp;quot; , PASSWORDD= &amp;lt;encrypted-password&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
===CONNECTSTRING (DSN Less)===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  CONNECTSTRING=&amp;quot;&amp;lt;Driver&amp;gt;={Microsoft Access Driver (*.mdb)} DBQ=C:\inetpub\wwwroot\BegASP\Chapter.14\Contact.mdb&amp;quot; [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sample Connection Strings:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w SQL Login:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;SERVER=server;Initial Catalog=database;UID=username;PWD=password&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;password&amp;quot; is the [SQL Server Password].&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w Windows Authentication:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;Initial Catalog=database;Persist Security Info=True;MultipleBC_TableResultSets=True; Database=database;SERVER=server&amp;quot;   User=username  Password=BR_PASSWORD&lt;br /&gt;
&lt;br /&gt;
Notice that the connection string, placed within quotes, is separate from the &#039;&#039;&#039;User=&#039;&#039;&#039; and &#039;&#039;&#039;Password=&#039;&#039;&#039; parameters. If the additional parameters are specified, BR will augment the connection string with &#039;&#039;&#039;UID=&#039;&#039;&#039; and &#039;&#039;&#039;PWD=&#039;&#039;&#039; parameters. Do not confuse connection string valid parameters with BR augmentation parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database]. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
BR_PASSWORD will use the users Active Directory password to connect to the SQL Server.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SQL Server&amp;quot; is one of several choices for SQL Server, another choice would be SQL Server Native Client 11.0.&lt;br /&gt;
&lt;br /&gt;
===ODBC-MANAGER===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt; ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
Using ODBC Manager as the parameter will allow the end user to select the desired data source.&lt;br /&gt;
&lt;br /&gt;
The following is a sample program that will use the ODBC-MANAGER to identify the proper connection string.&lt;br /&gt;
* Note: This will not work in Client Server because the operation is performed on the server and this process depends on the user&#039;s settings.&lt;br /&gt;
&lt;br /&gt;
 00010   PRINT Newpage&lt;br /&gt;
 00011   IF Env$(&amp;quot;br_model&amp;quot;)=&amp;quot;CLIENT_SERVER&amp;quot; THEN PRINT &amp;quot;Warning, you cannot connect to ODBC-MANAGER in Client Server Mode&amp;quot;&lt;br /&gt;
 00020   EXECUTE &amp;quot;Config Database Test_DB ODBC-MANAGER&amp;quot; ERROR CONNECT_ERROR&lt;br /&gt;
 00030   PRINT &amp;quot;Connection String is:&amp;quot; !:&lt;br /&gt;
        PRINT Env$(&amp;quot;STATUS.DATABASE.[TEST_DB].CONNECTSTRING&amp;quot;)&lt;br /&gt;
 00099   STOP &lt;br /&gt;
 00100 CONNECT_ERROR: ! &lt;br /&gt;
 00110   PRINT &amp;quot;Error Connecting - Err:&amp;quot;;Err;&amp;quot; Line:&amp;quot;;Line;&amp;quot; Syserr:&amp;quot;;Syserr$&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:All Parameters]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11433</id>
		<title>DATABASE</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11433"/>
		<updated>2023-07-31T01:25:25Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* CONNECTSTRING (DSN Less) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In version 4.3 and higher [[CONFIG]] DATABASE may use one of 3 methods for connecting to SQL Sources:&lt;br /&gt;
*DSN&lt;br /&gt;
*CONNECTSTRING&lt;br /&gt;
*ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
===DSN===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  DSN=&amp;lt;dsn-ref&amp;gt;  [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
The ? indicates prompt&lt;br /&gt;
&lt;br /&gt;
Example: &lt;br /&gt;
 EXECUTE &#039;CONFIG DATABASE CLS_Data DSN=&amp;quot;MyData&amp;quot; , PASSWORDD= &amp;lt;encrypted-password&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
===CONNECTSTRING (DSN Less)===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  CONNECTSTRING=&amp;quot;&amp;lt;Driver&amp;gt;={Microsoft Access Driver (*.mdb)} DBQ=C:\inetpub\wwwroot\BegASP\Chapter.14\Contact.mdb&amp;quot; [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sample Connection Strings:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w SQL Login:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;SERVER=server;Initial Catalog=database;UID=username;PWD=password&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
&amp;quot;password&amp;quot; is the [SQL Server Password].&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w Windows Authentication:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;Initial Catalog=database;Persist Security Info=True;MultipleBC_TableResultSets=True; Database=database;SERVER=server&amp;quot;   User=username  Password=BR_PASSWORD&lt;br /&gt;
&lt;br /&gt;
Notice that the connection string, placed within quotes, is separate from the &#039;&#039;&#039;User=&#039;&#039;&#039; and &#039;&#039;&#039;Password=&#039;&#039;&#039; parameters. If the additional parameters are specified, BR will augment the connection string with &#039;&#039;&#039;UID=&#039;&#039;&#039; and &#039;&#039;&#039;PWD=&#039;&#039;&#039; parameters. Do not confuse connection string valid parameters with BR augmentation parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database]. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name].&lt;br /&gt;
&lt;br /&gt;
BR_PASSWORD will use the users Active Directory password to connect to the SQL Server.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SQL Server&amp;quot; is one of several choices for SQL Server, another choice would be SQL Server Native Client 11.0.&lt;br /&gt;
&lt;br /&gt;
===ODBC-MANAGER===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt; ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
Using ODBC Manager as the parameter will allow the end user to select the desired data source.&lt;br /&gt;
&lt;br /&gt;
The following is a sample program that will use the ODBC-MANAGER to identify the proper connection string.&lt;br /&gt;
* Note: This will not work in Client Server&lt;br /&gt;
&lt;br /&gt;
 00010   PRINT Newpage&lt;br /&gt;
 00011   IF Env$(&amp;quot;br_model&amp;quot;)=&amp;quot;CLIENT_SERVER&amp;quot; THEN PRINT &amp;quot;Warning, you cannot connect to ODBC-MANAGER in Client Server Mode&amp;quot;&lt;br /&gt;
 00020   EXECUTE &amp;quot;Config Database Test_DB ODBC-MANAGER&amp;quot; ERROR CONNECT_ERROR&lt;br /&gt;
 00030   PRINT &amp;quot;Connection String is:&amp;quot; !:&lt;br /&gt;
        PRINT Env$(&amp;quot;STATUS.DATABASE.[TEST_DB].CONNECTSTRING&amp;quot;)&lt;br /&gt;
 00099   STOP &lt;br /&gt;
 00100 CONNECT_ERROR: ! &lt;br /&gt;
 00110   PRINT &amp;quot;Error Connecting - Err:&amp;quot;;Err;&amp;quot; Line:&amp;quot;;Line;&amp;quot; Syserr:&amp;quot;;Syserr$&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:All Parameters]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
	<entry>
		<id>https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11432</id>
		<title>DATABASE</title>
		<link rel="alternate" type="text/html" href="https://brwiki2.brulescorp.com/brwiki2/index.php?title=DATABASE&amp;diff=11432"/>
		<updated>2023-07-31T01:17:49Z</updated>

		<summary type="html">&lt;p&gt;Gordon.dye: /* DSN */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In version 4.3 and higher [[CONFIG]] DATABASE may use one of 3 methods for connecting to SQL Sources:&lt;br /&gt;
*DSN&lt;br /&gt;
*CONNECTSTRING&lt;br /&gt;
*ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
===DSN===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  DSN=&amp;lt;dsn-ref&amp;gt;  [, USER= &amp;lt;department&amp;gt; | LOGIN_NAME | ? ] [, PASSWORD= &amp;lt;dept-password&amp;gt; | BR_PASSWORD | ? | PASSWORDD=&amp;lt;encrypted-password&amp;gt; ]&lt;br /&gt;
The ? indicates prompt&lt;br /&gt;
&lt;br /&gt;
Example: &lt;br /&gt;
 EXECUTE &#039;CONFIG DATABASE CLS_Data DSN=&amp;quot;MyData&amp;quot; , PASSWORDD= &amp;lt;encrypted-password&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
===CONNECTSTRING (DSN Less)===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt;  CONNECTSTRING=&amp;quot;&amp;lt;Driver&amp;gt;={Microsoft Access Driver (*.mdb)} DBQ=C:\inetpub\wwwroot\BegASP\Chapter.14\Contact.mdb&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sample Connection Strings:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w SQL Login:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;SERVER=server;Initial Catalog=database;UID=username;PWD=password&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database] &lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name]&lt;br /&gt;
&amp;quot;password&amp;quot; is the [SQL Server Password]&lt;br /&gt;
&lt;br /&gt;
Using a SQL Server /w Windows Authentication:&lt;br /&gt;
 CONFIG database db-ref connectstring=&amp;quot;DRIVER=SQL Server;Initial Catalog=database;Persist Security Info=True;MultipleBC_TableResultSets=True; Database=database;SERVER=server&amp;quot;   Login_Name=username  Password=BR_PASSWORD&lt;br /&gt;
&lt;br /&gt;
Notice that the connection string, placed within quotes, is separate from the &#039;&#039;&#039;Login_Name=&#039;&#039;&#039; and &#039;&#039;&#039;Password=&#039;&#039;&#039; parameters. If the additional parameters are specified, BR will augment the connection string with &#039;&#039;&#039;UID=&#039;&#039;&#039; and &#039;&#039;&#039;PWD=&#039;&#039;&#039; parameters. Do not confuse connection string valid parameters with BR augmentation parameters.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;db-ref&amp;quot; is the database reference.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;server&amp;quot; is the SQL Server [FQDN] or IP Address&lt;br /&gt;
&lt;br /&gt;
&amp;quot;database&amp;quot; is the [SQL Server Database] &lt;br /&gt;
&lt;br /&gt;
&amp;quot;username&amp;quot; is the [SQL Server User Name]&lt;br /&gt;
&lt;br /&gt;
BR_PASSWORD will use the users Active Directory password to connect to the SQL Server.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;SQL Server&amp;quot; is one of several choices for SQL Server, another choice would be SQL Server Native Client 11.0&lt;br /&gt;
&lt;br /&gt;
===ODBC-MANAGER===&lt;br /&gt;
Syntax:&lt;br /&gt;
 CONFIG DATABASE &amp;lt;db-ref&amp;gt; ODBC-MANAGER&lt;br /&gt;
&lt;br /&gt;
Using ODBC Manager as the parameter will allow the end user to select the desired data source.&lt;br /&gt;
&lt;br /&gt;
The following is a sample program that will use the ODBC-MANAGER to identify the proper connection string.&lt;br /&gt;
* Note: This will not work in Client Server&lt;br /&gt;
&lt;br /&gt;
 00010   PRINT Newpage&lt;br /&gt;
 00011   IF Env$(&amp;quot;br_model&amp;quot;)=&amp;quot;CLIENT_SERVER&amp;quot; THEN PRINT &amp;quot;Warning, you cannot connect to ODBC-MANAGER in Client Server Mode&amp;quot;&lt;br /&gt;
 00020   EXECUTE &amp;quot;Config Database Test_DB ODBC-MANAGER&amp;quot; ERROR CONNECT_ERROR&lt;br /&gt;
 00030   PRINT &amp;quot;Connection String is:&amp;quot; !:&lt;br /&gt;
        PRINT Env$(&amp;quot;STATUS.DATABASE.[TEST_DB].CONNECTSTRING&amp;quot;)&lt;br /&gt;
 00099   STOP &lt;br /&gt;
 00100 CONNECT_ERROR: ! &lt;br /&gt;
 00110   PRINT &amp;quot;Error Connecting - Err:&amp;quot;;Err;&amp;quot; Line:&amp;quot;;Line;&amp;quot; Syserr:&amp;quot;;Syserr$&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:All Parameters]]&lt;/div&gt;</summary>
		<author><name>Gordon.dye</name></author>
	</entry>
</feed>