From: dent@DIALix.oz.au (Andrew Dent)
Subject: Lots of Fox hints
Organization: DIALix Services, Perth, Western Australia
Date: Sun, 27 Sep 92 08:48:05 GMT
Summary: Some accumulated wisdom
Keywords: FoxBase+/Mac


ABOUT THESE NOTES
All copyright to these notes is hereby relinquished and I transfer them to the
public domain. However, please leave this message at the top of the notes. If
you have any corrections or additional hints, forward to me and I will
incorporate them in the notes for future release.

24th September 1992

Andy Dent
A.D. Software, 94 Bermuda Dve, Ballajura Western Australia 6066
phone/fax 61-9-249-2719
CompuServe 100033,3241
Internet   dent@dialix.oz.au

PS I have a couple of nifty shareware XCMDS for double-clicking Browse &
external scrolling list dialogs, with their own arrays!



PUBLIC VARIABLES MUST ALSO BE INITIALISED 
It isn't enough to just declare variables as Public, you also need to assign
values before using them in a Get.

Strings must have at least one character assigned - a null string is not enough!

If a Public variable is shown as Hidden to an interrupt procedure (eg: ON MENU)
then it is probably being referenced indirectly by a procedure called from a
higher level - check for a private variable with "@publicVarName" instead of its
contents being shown.

--------------------==========--------------------==========--------------------

PASSING PARAMETERS
1) DO ... procedures (NOT UDF's)
If we pass an expression, the PARAMETER variable is PRIVATE.  However, if we
pass a variable, the value of the PARAMETER variable is copied back to the
caller, even if they are different names!  So, in Pascal terms, passing an
expression gives you Call-by-value and passing a variable gives you
Call-by-reference. A DISPLAY MEMORY will show the local var as referring to the
public var, and the public var will be Hidden from any routines that might want
to change it!

WARNING - this technique only works in procedures called by DO and doesn't apply
to UDF's - they are always call-by-value!


2) Arrays & other variables in UDFs
If we pass the quoted name of the parameter, we can use the macro substitution
operator to get the name of the actual variable - either PUBLIC or PRIVATE to
someone higher in our calling hierarchy.
ie:
PROCEDURE A
aLocal(1) = 1
DO B WITH "aLocal"
...

PROCEDURE B
PARAMETER numVar
&numVar(1) = 2
RETURN

Note that this trick works with arrays as the macro substitution is
performed on the variable name before applying the subscript.  Conversely,
this is a big gotcha - if you want to substitute something in an array you
have to copy it to a temp var & substitute that (usually doing something with
filenames and a command such as REPORT FORM).

--------------------==========--------------------==========--------------------

CHANGING WINDOWS.
Any menu click cancels the current READ.  If you do a MENU ON -3,1 this 
enables the trapping of window clicks as if they were in a menu.  They will
still bring the window forward but you at least get the chance to detect and
react to the change.  

If we want to make the background window active, we need to do something about
saving our current context.  We can't afford to nest procedure calls for
window-flipping - have to save context and exit procedure. Otherwise, user will
build stack of procedure calls in order of calling windows, unloading the stack
only when they terminate given windows. Your top loop should have some form of
CASE statement to react to returns to that loop, and execute the appropriate
procedure. All procedures will then simply RETURN when their window is
deactivated, saving their context somehow so they can carry on when the user
clicks on their window again (a really nice touch would be to save the field
name and have a routine that jumps down to that field by stuffing down-arrows or
tabs into the keyboard buffer).

--------------------==========--------------------==========--------------------

USING ARRAYS TO TALK TO FILES
If we use the neat trick of copying everything into an array, we have the
problem of how to initialize the array.  A transparent method is to make an
empty COPY STRUCTURE of the target file and do a SCATTER TO with that file, to
both clear the fields of the array and define them properly.  Note that you
can define the array with a PUBLIC &arrayName(FCOUNT()) assuming you are
copying all the fields of the array (remember that SCATTER skips MEMO's!!!).

--------------------==========--------------------==========--------------------

ARRAYS PREVENT FIELD-LEVEL CONTEXT-SENSITIVE HELP
SYS(18) returns only the array name for array cells used in GETs.  This
means it can't be used in UDFs to determine the current field, whether
for HELP topic indexing or for validation purposes.

--------------------==========--------------------==========--------------------

FINDING DUPLICATE RECORDS (Untested idea)
You can use an index to find records that are duplicates on the key expression,
using the delete facility to hide the others, and maybe a separate field in the
database isDup to mark them for later processing:
  recall all
  set unique on
  reindex
  delete all
  set order to 0
EITHER
  set deleted on
  && you now see only the duplicates!!!!!!
  set deleted off
OR
  locate for !deleted()
  ...
  continue
OR
  replace all isDup with .F.
  replace isDup with .T. for !deleted()

* to cleanup
  recall all
  set unique off  

--------------------==========--------------------==========--------------------

RELATED INDEXES
If you have indexes using a related field in their expressions
(eg:  INDEX ON UPPER(People->surname)) then you need to REINDEX every time a
key field is changed in the related file (ie:  field mod, rec add or rec del).
This is because FoxBASE can update its local indexes but doesnt know if
anything is related TO the file. 

--------------------==========--------------------==========--------------------

FILTERED INDEXES
An INDEX ON statement can have a FOR clause.  This is a quick way to build
throw-away indexes that point to a current set of records - much more
efficient than repeated operations using a LOCATE or setting a
SET FILTER TO expression.

eg: INDEX ON FOR DELETED() lets you use the Delete marking in Browse to select a
set of records.
WARNING - using the Deleted() function is NOT reliable between sessions and
doesn't work multi-user. If you want to ignore deleted records then use your own
Logical field (eg: deleted) and create an index like:
INDEX ON UPPER(catcode+itemcode) FOR !deleted TO "D:CodeDescr.idx"

You can then scan through these deleted records by doing
SET ORDER TO 0   && turn off index so you can see all recs
LOCATE FOR deleted
...
CONTINUE

OR

If you will have a LOT of changes to the file, and want to get deleted records
fast for reindexing, have a second index "on recno() for deleted" and just
  set order to 2
  go top
  if !found() 
    append blank
  endif

--------------------==========--------------------==========--------------------

RELATIONS NOT UPDATED (From CompuServe)
When you REPLACE the primary key of a relation, so you should now be related to
a different child record, FoxBase+/Mac doesn't update the relational link
automatically (see below). You can force an update with GOTO RECNO().

Cross-platform warning - FB2.1 for DOS does update the link but reportedly
FoxPRO is same as Mac.

  parent child
  key    key
  1      1
  2      2
  use child index key
  select 2
  use parent
  set relation to key into child
  go top
  ?key,child.key
  1 1
  ?lock()
  .t.
  replace key with 2
  unlock &&!!!
  ?key,child.key
  2 1 &&!!!

--------------------==========--------------------==========--------------------

PUTTING WINDOWS TO TOP
If we respond to window events, we have to leave windows on top of us if they
are DA's.  This isn't a problem under Multifinder but, under Finder, we need
to insert code to trap these windows.  In the code that responds to the MENU
of -3, add the lines:
IF VAL( SYS(1035) ) = -1 THEN
  READ
ENDIF
This READ will be interrupted by the next window event which will make a
reentrant call to our ON MENU procedure and then return, terminating the READ
and letting the original invocation of the ON MENU procedure carry on.

--------------------==========--------------------==========--------------------

USING BROWSE WINDOWS
You need the SAVE option for BROWSE to let you use your menus, and then have the
issue of detecting whether to return to the browse after a screen change. If you
set a flag to say ignore screen changes (because you went into a screen that
returns to the list) you can check with VAL(SYS(1025))<>-8 meaning BROWSE isn't
top and (if return flag not set) therefore assume it's been closed.

To programmatically close a BROWSE ...SAVE window you may be able to 
KEYBOARD(chr(23)) to put a ctrl-W into the typeahead buffer. (I have tried and
failed but someone else's system works, so it's probably a bug in mine).

If you still have a BROWSE active in the background and programmatically change
the current record, before initiating a READ, the record will change back to the
current BROWSE record.

--------------------==========--------------------==========--------------------

BEEPING THE USER
??chr(7) prints a bell character (on whatever screen is current - it doesn't
matter) which beeps in the current system beep. The double ? means no line advance
occurs so it doesn't affect the current screen display.

--------------------==========--------------------==========--------------------

ADDING ITEMS TO A LIST
A nicer way to display shortened names is as follows:
IIF( LEN( TRIM(var) )>30, LEFT(var,29)+"",TRIM(var))

--------------------==========--------------------==========--------------------

DUAL LISTS IN A WINDOW
You can have more than one list (within the limits of the 64kb of MVARSIZ) but
have trouble with who clicked where. A VALID UDF can be used to set a global
variable indicating which of the lists was last clicked. The only flaw with this
technique is the visual - you can't turn off the array selector on the other
list(s) because the VALID can't affect current GETS.

--------------------==========--------------------==========--------------------

SPEEDING UP SCREENS (tip from "Dynamics of FoxBASE+/Mac Programming")
If screen format is generated with the painting tool, it will contain a lot of
unnecessary COLOR, FONT and STYLE clauses.  Removing these speeds up the screen
considerably. 
(From my tests)
It seems safe to remove the COLOR clause from all except default button frames.
The FONT clause can be removed or the name or size removed when that matches the
default. The STYLE can be safely removed when 0, 1 or 65536 but looks funny when
taken off default button.

--------------------==========--------------------==========--------------------

SPEEDING UP SCREENS WITH "SET FORMAT TO"
Screens are a LOT faster when used in a .FMT file, via SET FORMAT TO instead of 
including the contents of the .FMT before your READ.

WARNING: when using SET FORMAT TO there are offscreen buffers declared for
imageing the screen prior to showing it (which is how it is so slick to
display). If you are running in colour then these buffers are around THREE
HUNDRED kb for a single Mac II-size screen. Be careful to dispose of these
screens by a SCREEN n DELETE  and avoid nesting too many screens (eg:
"drill-down" through 4 levels of 256 colour screens (8-bit) uses over 1Mb of RAM
just for the screen buffers, if all are SET FORMAT TO type screens. Consider
giving the user the choice of speed vs memory and DOing the .FMT files to avoid
allocating the memory for the offscreen buffers.

--------------------==========--------------------==========--------------------

SOMETHING TO TEST - SPEEDING UP PICTURE BUTTONS.
Try having one background picture which contains all the buttons and using
"invisible" picture buttons with the \F option.
We already know that icons are faster than picture buttons - this
technique may be even faster and can be used when the picture is too big for an
icon representation. It certainly cuts down the number of resources being
invoked by a screen.

Note that the flip-side of using icon buttons instead of picture buttons is that 
you may want picture buttons smaller than the standard (232x32) icon mask.

--------------------==========--------------------==========--------------------

OPTIMISING MEMO FILES
If you have frequently modified/deleted memo and/or picture fields then the
variable length
memo file will become internally fragmented over time.

To clean up the file, simply do a COPY ... TO command to create another database
and then rename the .dbf & .dbt files back to the original names.

--------------------==========--------------------==========--------------------

WORKAREAS
(suggestion) If files are permanently assigned to a workarea, a variable is
declared that's the workarea number, so it can be used as a parameter to
commands such as REINDEX.  The variable should be the same name as the file, eg:

SELECT A
USE Projects
Projects = SELECT()

--------------------==========--------------------==========--------------------

SAVING & RESTORING THE CURRENT SELECTION
You can save and restore the currently selected workarea by getting a STRING
version of the workarea number. It has to be a string so it can be translated by
the "&" substitution operator,eg:

curSel = str(select())
...... do lots of other stuff
select &curSel

--------------------==========--------------------==========--------------------

GOTCHA WITH "REPLACE"
REPLACE can only be used reliably on the current file, even though it is
legal syntax to do a replace on fields in another file, it won't always save!

--------------------==========--------------------==========--------------------

NEED FOR QUOTED FILENAMES OR PATHS
Historically, commands involving files or directories were mean't to be typed in
as direct commands and so a shortcut was used in not requiring the literal
filenames to be quoted (unlike literal strings in other contexts).

On the Mac, you MUST have quotes around filenames with spaces and some other
characters. There's nothing stopping you having quotes around a name even when
it has no spaces.

Thus, the easiest is something like
  aPath = chr(34) + aPathVariable + chr(34)
  set default to &aPath

--------------------==========--------------------==========--------------------

QUOTED STRINGS
The PUTFILE function returns a quoted string.  The quotes go into your variable
so you have to strip them. The alternatives are
filePath=PUTFILE(...)
filePath=&filePath
  OR
filePath = substr( filePath, 2, len(filePath)-2)

You can't include quotes in a quoted string by doubling-up (as in most
languages) so you have to use chr(34) as shown earlier. If you are using chr(34)
a lot it is a good idea to assign it to a global variable (eg: qt).

To include quotes in a message, use the curly quotes (option-[ and
shift-option-[) eg: msg = "Sorry, I don't know Harry"

--------------------==========--------------------==========--------------------

CUSTOMISING REPORTS FOR DIFFERENT PRINTERS (under test)
The (acknowledged) problem is that page setup orientation reverts to Portrait
for different printers  we need a way to save the Page Setup without having to
have a version of each report .FRX saved for each possible printer.

There IS a technique that enables you to have one set of report files, and
customise them at runtime to have the correct setup for the current driver.

Basically, you need a resource copying XCMD, like Rinaldi's CopyRes (there are a
few around).

If you have a FoxUser file, the Page Setup command updates the PREC resource in
there. Note: this updates the original FoxUser file, opened from the same folder
as your FoxBase or FoxRun application. It doesn't apply to any later SET
RESOURCE TO file, regardless of name.

You can copy this PREC resource, using the XCMD, to your report .FRX file.

--------------------==========--------------------==========--------------------

VECTORING TO DIFFERENT ROUTINES (implementation of OOP)
A context variable can be used for a CASE which selects amongst DO statements.
(eg: a Delete procedure which has a CASE to handle each different file). This is
a POOR solution as it groups by function rather than grouping functions together
into the one object. It is also very vulnerable to accidentally editing other
code and thus makes it impossible to "tickoff" an area as tested.

A faster and neater way is to use the context variable to build a procedure name
which is used by something like EG: 
G_file = WORK 
DO NEW_&file           &&  is translated to DO NEW_WORK

Another approach is to have a single routine (eg: Client) which takes a
parameter which is a string describing the message (eg: "Show Associated Cases")
and which is checked inside a big CASE statement. This is a bit slower but lets
you write much cleaner and more readable code.

--------------------==========--------------------==========--------------------

XCMD PARAMETERS
Parameters into and the return value of XCMD's are limited to 255 characters and
can't include arrays. However, the GetGlobal and SetGlobal callbacks allow you to
transfer more text (possibly limited by Hypercard's original 32kb - I haven't 
tested that size).

Also, you can use GetFieldByName to get the result of array expressions, so you can
transfer Fox arrays into/out of your XCMD cell by cell.

Get FieldByName also evaluates functions such as RECNO().

--------------------==========--------------------==========--------------------

XCMD QUICKDRAW GLOBALS
Inside an XCMD (at least, one created with Think C) your Quickdraw globals don't
point to the right place as they are A4-based.

You can copy the real globals to yours with code as shown below (applec is defined
if using MPW instead of Think C).

// COPY REAL QUICKDRAW GLOBALS TO THOSE WE SEE IN OUR A4-BASED GLOBALS
	{
		char *realGlobalsAt = *((char **) CurrentA5);
#ifdef applec
		BlockMove(realGlobalsAt-126, &qd, sizeof(qd));
#else
		BlockMove(realGlobalsAt-126, &randSeed, (long) 126);
#endif
		
/* NOTE: if you don't cast constant sizes to BlockMove you only update the low
         word of D0 and so you get whatever was in the high word taken as part
         of your length - probably resulting in MASSIVE overwriting of memory!
*/
	}

--------------------==========--------------------==========--------------------

NEW FEATURES IN FOXBASE/MAC VERSION 2.0 (by deduction, they have never listed them)

XCMDS

HIERARCHICAL MENUS

ReportWriter

Allow items in Apple, File & Edit menus (using negative menu numbers)

Detect window changes with menu -3,1

New options on BROWSE (not in manual - look at Help)

SCREEN ... FIXED to create modal screens

Scatter & Gather

Fasterr