Wiki Home

VFP Gotchas Language


Namespace: VFP
Idiosyncrasies in VFP that may surprise the uninitiated.

See also VFP Gotchas
Paths and files built into .exes.
report form /somedir/otherdir/myrepo.frx
those dirs may not even exist, but because that is where the file was when an .EXE was built, VFP finds it. I forget what file() does in this case.
FOR EACH, when used with a Collection of Objects, causes unpredictable results. EG, iterating through the MAPIFolders in an Outlook(!??!) MailBox will not preserve the objects for later use.

As above, use FOR x = 1 to lnSomeNumber -- Tim Hustler
Note: this issue with MAPIFolders is a COM Collection, not a VFP collection, and therefore doesn't necessarily mean FOR EACH is problematic with VFP collections, though it might be. -- ?wgcs

Haven't had any issues with "FOR EACH vs Collections" in VFP8 so far, tho i've yet to re-look at my old VFP6 Outlook code to see if FOR EACH works there or not now -- Tim Hustler
When using functions in the field list of the SQL SELECT command, VFP makes the result cursor's field widths match the result for the first row. For example:
SELECT TRANSFORM(ID) AS cID INTO ARRAY myArray

will result in the cID column being one character wide, if the first record's ID=1, even if the ID grows larger than 9 in later records, so the record with ID 10 will result in a row with cID="1"

You can use STR() or PADL() or PADR() for resolving this, depeding on how you want to display the number -- Peter Crabtree

You can accomplish numeric sizing via select 00000 + F(field) as Calc1 this would make the field N(5).

VFP9 solves the problem more gracefully with CAST():
Select Cast(0 as n(7,2)) as MyNumField from MyTable

-- Alex Feldstein
CURSORTOXML(nWorkArea | cTableAlias, cOutput [, nOutputFormat ;
           [, nFlags [, nRecords [, cSchemaName [, cSchemaLocation [, cNameSpace ]]]]]])

The parameters for this function are fairly different than most of the other parameters that are needed for VFP commands/functions:
  • It does not default to the current work area
  • It does not return the XML as a return parameter, instead it returns the lengh of the string that was put somewhere
  • The 2nd parameter specifies either be a filename or a variable name. So if you want the xml in a var, put the name of the var in quotes.
  • If you want XML that will work best with VFP's Xml To Cursor() you need to set some of the params. (Will someone post what they are? I can't figure it out.) Here is what work best for me:
    CURSORTOXML ("MyTable", "MyVar",  1, 0, 0, "1")
    --- SL
    'Names' (like the name of a table) do not need delimiters. USE FOO is the same as USE "FOO". Name Expression is a whole topic on it.
    Arrays.
    1. VFP does not have the capacity for a zero length array. All arrays have at least one element.
    2004/04/05 Steven Blake - See Array Zero Length for discussion and code
    2. All VFP functions that populate an array will not do anything to it if there would be no elements. If there were elements before, they are left there. These functions return the number of elements, so you can test for 0 to see if it did anything.

    SELECT INTO ARRAY, COPY TO ARRAY work the same way. In this case _TALLY can be used to get the # of records stored into an array.

    aDel( lax, 1 ) - deletes the row, but doesn't resize the array. Leaves 'junk' at the end.
    Passing Arrays can be 'tricky.'

    Why, oh Why, do VFP arrays start at element[1], when pretty much every other array you're likely to come across in a language will start at element[0]?!? What happend to laArray.Length as well?? We have aLen() admittedly, but it's not quite the same is it? Tim Hustler
    "pretty much every other array" refers to VB and C. In Delphi, the array can start and end at any index. The syntax "laArray.Length" makes no sense except in VB: if laArray[1] means anything, laArray.Anything should be meaningless. Only the VB "default properties" kludge makes laArray[1] and laArray.Length work together. Of course, it all comes down to what you are used to, then going to a new environment. -- wgcs
    SET isn't local. There are about 100 settings that can be set using the SET command. There is no good way to make sure things are set the way you want them, or that your settings arn't reset by some bit of code. These settings have an effect on all sorts of commands. For instance, SET MEMOWIDTH has an effect on most string processing that deals with multi line strings, like MemLines().

    "There is no good way..." Huh? I would tend to disagree with this statement. Take a look at any decent framework. There should be classes that handle global and session settings very nicely. I know Visual FoxExpress has this. Also, like anything else dealing with good coding practice, if you change a SET setting explicitly, make sure you change it back to the original setting. -- Randy Jean

    Agreed. I'd say the the "Gotcha" in SET has to do with the fact that some are scoped to the DATASESSION. - ?lc

    The part of this that I consider a "gotcha" (at least it got me) is the following:
    While the fox docs tell you that certain set functions are scoped to the datasession, I couldn't find anywhere (in the MS docs or here) where it told you how they are set when a datasession is newly created. Empirically, Foxpro seems to set the "sets" in a new datasession to some hardwired idea of what the "real real" defaults are (except for SET EXCLUSIVE). In particular, settings in the default datasession, or settings done from the menu system seem to be of no interest to Foxpro. My favorite is SET CENTURY, which I was surprised to find (a) is bound to the datasession and (b) defaults to a setting that microsoft themselves don't recommend. Michael Wagner

    P.S. Can anyone think of a good reason for this behaviour (either one, set century, or new datasessions inherit their settings from nowhere sensible).

    My issue is how easy it is to break the "good coding practice."

    Any language I've ever worked with makes it very easy to write bad code. However, I see your point with the SET commands as they are not always scoped local and can be changed without setting back. There is a class in our framework called cSet that will automatically handle restoring the old setting when the object goes out of scope, so that is one way you could insure that changes to SET command ARE scoped local.

    LOCAL loExact
    loExact = CREATEOBJECT("cset","EXACT","ON")
    
    ... some code here
    
    ** when object is out of scope, old setting of exact will be restored
    loExact = .NULL.
    


    Randy Jean

    you can have a field and a memvar that are the same name, but hold different values:

    cThing = "hello"
    CREATE CURSOR Foo ( cThing C (10) )
    INSERT INTO Foo VALUES ( "world" )
    ? cThing  && "world"
    cThing = "Mars"
    ? cThing && "world"
    USE
    ? cThing  && "Mars"


    Good coding practice: prefix all vars with m.
    Better coding practice. Prefix all variables with
    lcVarName (local, character, VarName)
    tuVarName (parameter, undetermined, VarName)

    Microsoft has programming standards documentation that describes all of its suggested prefixes. Much more informative and easier to read than m. all over the place. Ray Kirk
    Best coding practice: Use both m. and hungarian notation. Readability is in the eye of the beholder. m. all over the place just takes some getting used to. See Essential MDot for more information. Mike Yearwood
    Beware comments that end in a semicolon.
    In VFP7 (and before?) the following code will only print "Hi"

    ? "Hi"
    * This is a comment ending with a semicolon ;
    ? "This won't print"
    


    The comment is continued into the next line. While this is nice as it allows you to comment out entire statements like:

     * SELECT * ;
       FROM MyData ;
       WHERE .t. ;
       INTO CURSOR TEMP
    

    With a single *, the Editor in VFP7 (and some beta's of VFP8 it seems) does NOT colorize the commented code as such.
    This means comments ending in ; can lurk for years waiting to attack when the whitespace below them is removed, causing all sorts of possible odd compile errors (consider what may happen if the next line is DEFINE CLASS... , or worse, no compile error, consider if the comment was at the end of an oft called UDF, followed by another, not so frequently used UDF: PROCEDURE ZapAllTables().

    Well! I'm a big fan of granularity. Instead of arbitrarily putting all your UDFs in one PRG, make each one it's own PRG and you'll never experience that! See VFPSetProcedure -- Mike Yearwood

    Many people have gotten bit trying to embed the 'comment command' in a string: ? "&&" gives you Error# 36, "Command contains unrecognized phrase/keyword." because VFP sees 2 things: the code to execute: ? " and a comment right after it: &&" - workaround: ? "&"+"&"
    DELETE doesn't delete. It just marks it to be deleted by the PACK command, which in a 'normal app' (multi user) doesn't happen till way later (sometimes weeks.)

    You can SET DELETE ON which kind of hides the records, but they are still there and will block a candidate key if it is the same as what was deleted. And then there are the exceptions to this, which are explained in the docs. The best way to think about it is: you can't delete a record instantly. You can set a flag and the rest of your code should respect that flag. VFP gives us some commands to help, but you must still be aware that the record is there.

    The value RECCOUNT() returns isn't affected by SET DELETED and SET FILTER.

    Col = CREATEOBJECT("Collection")
    ? col.Add("Rick")
    ? Col(1).Name  && Error# 1230, "Too many arguments."


    That is because col() is a vfp function. I think the Col= should have errored with a "reserved word" error, but there is no such error, and it would probably blow up too many existing apps if one was added.

    > Along the same lines I also had problems when I used properties of the
    > object that matched VFP names like Name. changing Name to cName fixed
    > that as well.

    I am guessing it is because VFP only needs the first 4? chars to 'identify' a reserved word, like TableUpdate()....

    Whatever it is, VFP is not good about policing what are used as identifiers. To help deal with this, try to name your things something that doesn't stand a chance of being a reserved word. Most Naming Conventions take care of that for you.

    Use "m." (? m.Col(1).Name), if you (stupidly!) insist on using reserved words for variables! The idiosyncrasy here is that VFP allows you to use reserved words for things - but this is for backwards compatability. C++ programmers wouldn't try to name a variable "int"! -- Peter Crabtree
    Macro substitution is powerful but has some not so easy to understand idiosynchracies. Specifically the need for an extra "dot" to delimit a macro-expansion variable when another dot is required.
    cFileName = "ABC"
    cSequence = "1"
    IF FILE(&cFilename&cSequence..Dbf)
      * file exists
    ELSE
      * file not found
    ENDIF
    

    In the example above two dots are needed, one to end the macro expansion, and one to literally add a dot for the filename. If you had used only one dot, it will be "eaten" and considered the as end macro of expansion marker.

    Best answer to that is to always end every macro with the termination period. Mike Yearwood

    Note that in this case there is a more straightforward and cleaner way of referencing this filename:
    IF FILE(cFilename + cSequence + ".DBF")
      * file exists
    ELSE
      * file not found
    ENDIF
    

    Activepage in a Page Frame is not the index of the active page in the pages-collection, but the pageorder of the active page. As long as you don't resort your pages everything is ok...
    Look at: Page Frame
    When is something plus null not null? When that something is a string more than 100 characters in length. E.g.
    local x
    x = replicate('x',100)
    x = x + null
    ?x && returns null
    release x
    
    local x
    x = replicate('x',101)
    x = x + null
    ?x && returns xxxxxxxxxxxxxxxxx...
    

    Is there a reason for this? Rhodri C Evans

    But:
    local x
    local z
    x = replicate('x',101)
    z = x + null
    ?z && returns null
    

    Does this sound buggish?
    -- RushStrong

    It probably is a bug ... a long standing one, I can demonstrate the weird behaviour in VFP 6,7,8 & 9.

    Note also that
    local x
    x = replicate('x',100) + null
    ?x && returns null
    
    works as expected.

    Sounds like the VFP code to add to a string variable is bugged, not the code to add two variables together. (In other words, when VFP does x = x + y, then, internally, it becomes "x += y", which uses different code than "x + y". -- Peter Crabtree
    TRY .. CATCH and SET CONSOLE
    SET CONSOLE is changed to ON whenever an error occurs regardless of whether the error is trapped by a TRY .. CATCH. The help on SET CONSOLE only says "An error always sets SET CONSOLE to ON." but why would you want this behavior? Can anyone explain why this happens? And are there other settings that are automatically changed when an error occurs? -- Tom Bellin

    Tom, just ran into this myself. You're right, I can't think of why I would ever want SET CONSOLE to be ON in my app. Therefore, I've searched all my projects for TRY/CATCH and have issued a SET CONSOLE OFF immediately following any code in the CATCH portion. Good "catch" man. -- Randy Jean

    Grids
    • If you attempt to set the grid's columncount property equal to zero from a contained column or header object method, it will delete all but the column where the method is. For example, if you create a click method for the header that redefines the grid, setting columncount=0 inside that method will not eliminate the column where that header is located, but will eliminate all other columns. Solution: redefine the one remaining column instead of treating it as deleted.
    • Prior to VFP9, do not use SET FILTER TO on a large table that is being displayed in a grid. It can take a VERY long time to refresh the grid. I have seen this behavior in tables with as little as 10,000 records. If possible, you can use SET KEY TO without performance problems or better yet, use a SELECT statement or a view. By the way, I have not used VFP9 but apparently grids now can use rushmore optimization with SET FILTER TO if the grid's optimize property is set.
    • Grids automatically refresh themselves on frequent intervals to keep the rows of data displaying the correct and current data. This means that if you are simulaneously updating a row that is also displayed, you can experience locking conflicts. The workaround is to set the form property lockscreen=.t. in the form that contains the grid to prevent the grid from refreshing while your updates are being made.
    • A Textbox object in a grid column will allow you to click directly into the middle of a displayed number or character string. In many data entry scenarios, clicking into the middle of an existing number in a Textbox and then trying to type a new number is fraught with error and frustration since you are not typing over the entire number, just the last few digits of it. To eliminate this problem, use the following code in the GOTFOCUS event of the textbox.
      this.setfocus
      nodefault
      

      You should include "K" in the format property of the textbox.
      Now when you click on the textbox, it selects the entire contents of the box. When you start to type, the original number is completely erased and you starting from the leftmost cursor position. This trick also works for textboxes outside of grids. In addition, if you click a second time on the same textbox, you will still be able to place the cursor mid string or mid number if you want to. -- Ben Creighton.

    Another Grid Gotcha
    Grids do not respect SET RELATION TO.

    For example, I have parent table alias PARENT1 and a child table alias CHILD1 ordered by the foreign key for PARENT1. I select PARENT1 and set relation to PRIMARYKEY into CHILD1.

    Now set the grid's RECORDSOURCE property to PARENT1 and use any field from CHILD1 as one of the column fields. Note that the record pointer doesn't change in CHILD1; you get the same value for the column throughout the entire grid.

    Solution: Create and use as the CONTROLSOURCE a UDF that changes the record pointer in CHILD1 and returns the field data. So sorry if you wanted to actually edit the CHILD1 field. -- Ben Creighton
    nvl(x,y) and evl(x,y) always evaluate both x and y regardless of the value of x. It's like they have been implemented as UDFs, rather than as language components. -- Rob Spencer
    Contributors: Carl Karsten Sergey Berezniker Peter Crabtree Alex Feldstein Mike Yearwood Rhodri C Evans Rob Spencer
    Category Needs Refactoring
  • ( Topic last updated: 2006.05.02 09:30:59 AM )