Wiki Home

VFP Rookie Mistakes


Namespace: Wiki
A place to discuss those classic mistakes we've all made, hopefully to spare others some of the grief.

See also VFP Misused And Abused VFP Gotchas

Do these things and avoid some common mistakes:

Item Rookie Mistake
In some instances, especially at valuable junctures, CREATEOBJECT() and
NEWOBJECT() are evil.
The problem with CREATEOBJECT() and NEWOBJECT() is, to the extent they are sprinkled all over in code, they hard-code the birth and also, therefore, the subsequent configuration of class instances, all over the place.
Compare these two lines, they have the same effect, yet The line with oFactory.Create() is incalculably more flexible and potent than CREATEOBJECT().
x= CREATEOBJECT(“KeyCustomer”)   && hard-coded direct

y= oFactory.Create(“KeyCustomer”)  && delegation, parameter is just a locator.

Using the factory gives us the ability to do many things, including but not limited to:
  • Drive creation and configuration from metadata.
  • Hook the creation process.
  • Possibility of centralized error handling.
  • Endless other dynamic possibilities.
In short, if creating a particular object is a juncture for future adaptation or extension, then there is a lot to like about using a factory at the outset. Arguably, given that it's a surprisingly cheap instrument, easy to refactor-in, and so potent then it could be argued that delegating to a factory for object creation would be a sensible programming standard. I've certainly never regretted delegating to a factory.-- Steven Black

One thing you have to watch out for is the data session your object is created in when using a factory. This is especially problematic if the object is going to need to read data from a VFP cursor. Of course you could design your factory to switch its current data session to a passed in session id prior to creation. Another possiblity is to use the factory to provide the concrete class name based on the token hence code like...

x = CREATEOBJECT( oFactory.GetClassName("KeyCustomer") )

This still allows for object creation in the current datasession while allowing the factory to determine the concrete class name. -- Bob Archer

Yup, good point. In those circumstances you could always say:

oTempFactory = CREATEOBJECT("Factory")
x= oTempFactory.Create(“KeyCustomer”)

... which I think is better since the factory could/should not only encapsulate the creation of the object, but also its configuration, etc. So it's not always a matter of just delegating the hard-coding of the class name, but other services as well. -- Steven Black

(Perhaps an additional possible rookie mistake would be creating objects in the wrong data session.)


If you can't figure out why a form's Data Session remains open after the form (with private datasession) was destroyed check your code (including functions) for any objects (including collections) that get instantiated and assigned to a _screen property - that is most likely the reason for your zombie datasession :) -- edyshor


Date data type I'd like to float this as a potential huge rookie mistake for applications that may someday upsize to a backend database. Why? Because the Date data type is a VFP idiom, whereas backends typically store Datetime data types. Therefore if all your code assumes Date data types, then upsizing is a much more horrendous task than it would be otherwise. Thoughts?-- Steven Black
I've upsized a few fairly large projects to SQL and mapping the data in the date type to a datetime type was never a problem. I think the larger problem is knowing how to deal with a datetime type in SQL when you only want to use the date portion. I don't see a reason to make a system more complex when it uses native tables just [BE]cause you might upsize it someday.
In addition, SQL Server 2005 adds a date datatype. I have no knowledge of Oracle. -- Bob Archer
Take note: The problem isn't in the type mapping, it's with empty values, which in SQL server are either NULL, or an idiomatic long-ago date, neither of which will work in your legacy Fox app without significant work.-- Steven Black
Given your qualifier that the app may go to another backend in the future, then I would agree. For similar reasons, I have taken up the habit of coding logicals as numeric (1,0), which is common across the popular backends. The decision is really "pay now or pay later." The decision to use datetime against a VFP backend when you only need the date portion adds only a slight complication to date handling/date math. -- Guy Pardoe
One idea is to always support the concept of NULL in your VFP applications. One of the problems is not necessarily the DATE data type itself but the fact that VFP supports a blank date value which is a foreign concept to most backend database servers. Rod Paddock


If you are planning to use remote views then upsizing is not as painful because you can map the date type in the view even though its datetime type in the database. The only caveat is that we allow datetime fields to be nullable in the database and because we are binding to field classes we translate this in our value_access/value_assign accordingly. (actually, the VFE framework does this for us. If you need to do SPT (which we still do for OLAP and reporting operations) then you need to be aware of the datetime and null issues. As for the logicals, bit fields in SQL work nicely and natively map to VFP logicals, even in SPT (except in the where clause you still need 0/1). However, I know that Oracle did not have this type the last time I checked. Not sure about the MySQL and other backends -- Randy Jean
I agree about bit fields in SQL Server. But in my case, I worked on a project writing a vertical market app to support either VFP, SQL Server, or Oracle backends. One of the technical goals was to have as little variation as possible between choice of backends. I learned some lessons about common data types, reserved words, etc across those three backends. Also as stated above, I agree that it's worth the trouble to allow nulls in your VFP data if you think you're headed towards a SQL backend. -- Guy Pardoe

Include Files An include file contains compiler directives and since VFP compiles every program, class, and form separately, it is necessary to specify the include file for every program.
In the case of Forms and Classes, there is an option on the system menu (under Form for forms and Class for classes) to specify the Include file, using this menu option prevents the need to specify the include file in every method.


Note: The file included through Form, Include File is included with absolute reference. I had a problem with this appraoch since we create a new project for each new year. When I copied forms to a new folder, all forms continued to refer to the old include file. On the other hand, I can use "Include\MyIncl.H" in form methods, and easily copy forms and modify the "Include\MyIncl.H" in the new folder. -- Ravi
SET EXCLUSIVE The default value for SET EXCLUSIVE is ON. Thus when you create an EXE, if you don't set this explicitly yourself, this is what you get.

The default is only ON for the global datasession. The default is OFF for private datasessions.
Minimize the code you put in Refresh methods Don't put excessive code in Refresh methods. Especially, don't put SET FILTER TO or REQUERY() in a Refresh method. Refresh methods get called very often and excessive code will severely slow down the performance of your application. As George Goley said, "Speed up your applications by removing the slow parts", and lots of code in a Refresh method is a slow part.

Don't call Refresh excessively. Realize that calling Refresh for a container calls the Refresh for each of its contained objects implicitly.
In addition, having a DODEFAULT() in the refresh of a container will cause contained objects to refresh twice for every refresh of the container, even if followed by a NODEFAULT. So, it's best to stay away from behavior that must be inherited within a container's refresh method. You can get around this by having the refresh of a container's superclass call a separate method for any custom refresh behavior that you want to inherit and possible supplement in application classes.
SET DELETED Take the time to understand the effect of SET DELETED ON/OFF
Porting / Converting Don't port FoxBase or FoxPro code! It sucks. Don't do it. It's okay to say no. Get a job at a Microage store instead, if need be.

The code that is transported from FoxBase or FoxPro is often convoluted and difficult to maintain and understand. It is virtually NEVER the best way to accomplish any tasks and is therefore a very poor tool for learning VFP. It is much better to study the sample code included with VFP and/or available here and at other sites.
Grids Avoid data entry in a grid. Place buttons near the grid that give the user the ability to add/edit/delete. If add/edit, fire up a form and do all the data entry stuff in the form. When finished, make the form go away and refresh the grid to show the changes. Grids have MANY weirdnesses and quirks and this method gets around a lot of them.

Grids can be used for data entry, however, as a rookie you will need to deal with the user's requests for more and more customization over time and when grids begin to use controls other than the default textbox they can become complex beasts that are difficult to understand and maintain. It will be better for you in the long run to wait until you have more thorough understanding of grids and their quirks before you begin to use them for data entry.

If you need to set the data source of a grid to a cursor that gets re-created via code (sql select, or whatever), save the Record Source property, set the Record Source property to space(0), execute the code to re-create the cursor, then set the Record Source back to the saved value. This prevents all the columns from disappearing when the cursor momentarily disappears.
You can also save the individual controlsource properties of each column and then restore them. e.g.
WITH THIS.grdDtlLines
   LOCAL laControlSources[.ColumnCount],lnColumn
   FOR lnColumn=1 TO .COLUMNCOUNT
      laControlSources[lnColumn]=.COLUMNS(lnColumn).CONTROLSOURCE
      .COLUMNS(lnColumn).CONTROLSOURCE=[]
   ENDFOR
ENDWITH
.
< do the refresh data stuff >
.
WITH THIS.grdDtlLines
   FOR lnColumn=1 TO .COLUMNCOUNT
      .COLUMNS(lnColumn).CONTROLSOURCE=laControlSources[lnColumn]
   ENDFOR
ENDWITH

Alternatively, use a Grid Safe Select; see also Category Grid Basics.
EventsShould
Always
CallMethods
Don't place behavior code in the User Interface Layer. The issue here is to organize your code in the methods so it is most easily leveraged. If you find a line of code that says, Thisform.Someotherobject.Click(), you should probably create a new method for the form and move the code from Someotherobject.click() to the new method and then call that method from both the someotherobject.click() and the line that calls the click.
Get the Hackers Guide Get the Hackers Guide and put its CHM file on your hard drive, then place a shortcut to it in your Windows start button. Some developers even SET HELP TO HACKFOX.CHM or add it to their VFP menu at startup.

The Hacker's Guide is available from www.hentzenwerke.com
FoxProAdvisor, Fox Talk, and CODE journals Subscribe to FoxPro Advisor, Fox Talk, and CODE magazines. After doing this scour the websites of Advisor Publications and Pinnacle Publishing and bookmark their indexes of past articles.
While there, join our crusade to get these publishers (especially Advisor Publications) to put content online so you can get the documentary help you need within minutes of needing it.

Also, I've found that the Fox Talk CDs have superior search capabilities than the Advisor CDs. I buy the previous year and add to my harddrive.
There's almost always an easier solution You just have to ask someone who's been there. There are a lot of resources available for little or no cost. (see Where To Find Downloadable VFPSamples)

After you have reviewed what is already available, if you still feel you need to do things differently don't be afraid to experiment.
Consider using Views Study and experiment with the advantages and disadvantages of using views, particularly using views for data entry into grids, parameterized views instead of SET FILTER and parameterized views for data entry into child tables. Don't start your app working directly with tables only later to discover the advantages of views.
Don't reinvent the wheel The best VFP developers download or purchase far more code than they write themselves. Know Where To Find Downloadable VFPSamples and scour Category Frameworks.
Don't try creating a Frame Work as your first endeavor
A framework is a complex system and is not something to use to learn the language.
You will probably throw out your first VFP application. See the "don't reinvent the wheel" item above.
Frame Works Study a framework. It will show you how things can be done, as well as how to avoid the pitfalls of VFP. Visual Maxframe has a shareware version on their web site that is a worthwhile study. Also, the original Codebook is freely available and worth a look. You may decide that using a framework is worthwhile. It usually is, except for the simplest of projects.

Don't expect to be successful with any framework until you've first learned a considerable amount about OOP, data buffering, the database container and the VFP designers. With or without a framework you need to have a fair amount of VFP knowledge before you can be successful.

Hold the chainsaw by the correct end. Frameworks took years to design, build and refine, and will take time to master. Use the framework as it was intended. On your early outings, do things the way the framework expects you to, or you'll be treading uncharted territory where side-effects can make the framework your adversary. As you learn the intricacies of the tool, you can start to cautiously experiment with going outside the lines.

See Category Frameworks.
Pay attention to good data design When building tables, avoid Repeating Fields. When you see fields named Contact1, contact2, ... it is a really good sign that you are creating Repeating Fields.
Persistent Relationships in the DBC Defining a persistent relationship between two tables in a dbc is not the same as SET RELATION TO
Avoid using spaces in table names You'll have to deal with it in numerous places in your application and VFP will not use the table name as the alias which is the default behavior under normal circumstances.
Don't hard-code drive letters or paths Don't hard-code drive letters (or network) paths in application code or SET PATH command. This locks the application into only being usable in one directory structure (or network structure).
Avoid using Key/Reserved words Don't use key/reserved words for memory variables. eg - If you have a PACK of a dozen don't write PACK = 'DZ' You will execute the PACK command.


I agree one shouldn't use keywords for memory variables. However examples should make sense, no? I wonder in which version it is true that "If you have a PACK of a dozen don't write PACK = 'DZ' You will execute the PACK command.". I disagree with it. In all versions I know you will end with a memory variable named 'pack'. - Cetin Basoz

Also avoid using key/reserved words as field names in a table. You don't want to see a SQL SELECT like:
SELECT * FROM cTable GROUP BY group ORDER BY order INTO CURSOR Cursor
Screen Resolutions Determine the resolution of the screens that will run the application. Set the form designer to use this resolution (tools/options/forms/maximum design area).
How to Find the Current Video Resolution
Screen Resolutions Good idea is to also set YOUR screen to the >>minimum<< possible resolution your clients will be using (which is, these days, rapidly changing from 640x480 to 800x600 but if you're wanting to play it safe....) that way, you're sure it looks good (albeit small on the higher resolutions) on every screen resolution.

Actually, there are still a lot of people who run 640 x 480 even on 17" monitors. They simply can't read text on the screen at higher resolutions--and this includes many people who don't have "official" visual impairments, but whose eyes are just going through middle-age-related changes. ALWAYS ensure that your application can run in 640 x 480, and if you have the ability, eschew the use of operating systems that don't support 640 x 480. -- Ken Dibble

Remember that "640x480" is the whole screen, you might only have 640x400 available for a form. The Windows status bar, the VFP title bar and a menu all make the screen shorter. -- Geoff Franklin

We had the reverse problem, forms designed to work at 640x480 but too small for users with new 14" 1440x1080 laptops.

It would be even better to design your applications with accessibility in mind (font choices, colors, etc.). Couple that with some resize code (or use anchors in VFP9) and you're on your way. -- Tod Mckenna

Tables with no records Test your application for ability to withstand tables with no records, record pointers at end-of-file, large numbers of records.
Conditional Branching Whenever you use an IF..ENDIF statement, consider the need for ELSE handling. For DO CASE constructs, consider the need for an OTHERWISE.
FOUND() Whenever you Seek or Locate, test for FOUND() before assuming that the sought-after record actually exists. You may want to consider using the SEEK() function and checking its return value.
Valid Events Don't rely exclusively on the VALID method for enforcing data integrity. The valid will only fire if the user enters the control. So, if you enforce something like the field can't be blank, and code that in the valid of the textbox, it is possible that that valid code is never run, and the record is saved.

Better yet, you should develop your applications with an nTier architecture and put business logic (data validation) where it belongs: in the middle (or business) tier.
Toolbars and Menus never get focus Therefore the valid and lostfocus methods of the active control will not fire when the user navigates to a toolbar or menu. There are techniques for handling this but they are beyond the scope of this page.
Closing Forms
Be aware of all the ways a user can close a form and defend for them.
Closing Tables
Avoid this practice:
      IF !USED('sometable')
          USE sometable IN 0
      ENDIF
      ... do some work
      USE IN sometable
      

A program or method should leave things the way they were found (unless it is some sort of cleanup program I suppose). You can do something like this instead:
      LOCAL lopened
      IF !USED('sometable')
          lopened = .t.
          USE sometable IN 0
      ENDIF
      ... do some work
      IF lopened
          USE IN sometable
      ENDIF
      

-- Tod Mckenna

In addition to what Tod has suggested I would add that one needs to remember that SomeTable may be open with a different alias and protect against the table is in use error. Todd's code would be altered to ...
      LOCAL lopened
      IF !USED('sometable')
          lopened = .t.
          USE sometable IN 0 AGAIN ALIAS SomeTable
      ENDIF
      ... do some work
      IF lopened
          USE IN sometable
      ENDIF
      

-- Jim BoothOffsite link to http://www.jamesbooth.com
Reports
Place the name of your report in one of the bottom corners of the report. It will make it easier to find it again if you need to modify it later. Also this makes it so that when a user calls and says "This report dosn't work right", you can say "What does it say in the bottom left corner?" instead of "Oh, which of the four hundred reports in the system are you talking about?" and being told, "You know, the one I run on Tuesdays for the boss". Saves a lot of time.
IntelliSense
(VFP 7 and beyond) To avoid having IntelliSense add the "TO" keyword to SET commands (leading to things like SET DATABASE TO TO), go to Tools | IntelliSense Manager | Advanced | Custom Properties. Change the lEnableFullSetDisplay property to F. The default is T.
But a better choice is to live with and get used to it. For a while, you'll double up the TO's, but it won't take long before you get your fingers retrained. -- Tamar Granor
REPLACE conflicting with ORDER/INDEX
If you ever find that a "REPLACE foobar WITH foobar ALL" command is not performing as expected -- specificially that the all records currently FILTERed are not updated!!!
Confirm that the foobar field being REPLACEd is NOT involved in the currently active ORDER of the table (i.e. INDEX ON foobar+CODE TAG primaryIndex.) Resolution: use sOldOrder = ORDER() and SET ORDER TO / SET ORDER TO &sOldOrder to store/restore your ordering bracketing your REPLACE statement. -- Rick Hathaway
TableUpdate() takes 3 or 4 arguments. Use them.
TableUpdate has more than one argument. The arguments are:
  1. What records to update
    • 0 (only the current record)
    • 1 (all pending records and fail the update completely if any record fails)
    • 2 (all pending records and if any record fails continue with the rest and put the record numbers that failed in the array specified in the 4th argument)
  2. Force past update conflict (.T. will overwrite the changes of other users .F. will fail the update if another user has made changes
  3. The alias in which the update should occur
  4. The name of an array to store the record numbers that failed to update when first argument's value is 2
Always check the return value from TableUpdate()
There are many situations that can cause a TableUpdate to fail and only ONE of them is an update conflict. Among the other possible causes of failure are server crash, broken network cable, failure of a field or table validity check, failure of referential integrity checks, etc. etc.. Using the second argument as .T. (force past update conflict) does NOT guarantee that your TableUpdate will succeed. Your code should always check the return value and handle update failures.
Add RESOURCE=OFF in Config.fpw
Unless your application specifically takes advantage of the foxpro resource file (Fox User) add this to your config.fpw settings to prevent it from being created at startup. One less file to open and worry about screwing things up.
Note: if in doubt whether you need this, you probably don't - if you use a commercial framework, check with the vendor on whether they rely on Fox User dbf at runtime. This is kind of a "legacy" feature that was useful in the DOS days for managing colors and printer drivers by user or workstation. It also stores browse window settings, etc. (which may be useful in development, but not in deployment) -- Randy Jean
Defend against no printer being installed
Before you send any output to the printer (including with REPORT FORM), keep in mind that the user will see a run-time error if no printer has been installed. You can test for this by calling APRINTERS(). Mike Lewis
This is even a bigger issue than Mike states as sending report output to a file or via a Report Listener in VFP9 may result in a no-op or error if there are no printer drivers installed. John Koziol
Create a clean up macro
Assign a clean up macro to F4 or some other function key. See: Clean Up Code Carole Shaw

*OR* (another viewpoint)

Just put the following in the Macro, and use code in the the program to do the cleanup:

{CTRL+F2}{CTRL+END}DO X:\PATH\CLEANUP{ENTER}
-- Peter Crabtree
Added non-macro version to Clean Up Code topic. ?cs
Checking for optional parameters with PCOUNT()/PARAMETERS() I've seen this sometimes in functions where it is ok. But with objects and inheritance relying on the number of params leads to errors.
You misdiagnosed the source of the problem. PCOUNT() is correct way and only way to check for optional parameters. The problem is in incorrect passing parameters through. See corrected version below.
I disagree. The case solution is ugly, adds too much code, and depends on knowledge about foo's implementation in the baseclass (which is real bad design). And you can't add a parameter to myBaseclass::foo without breaking your code (or checking with VARTYPE/TYPE). -- Kurt
  obj = create("theAncestor")
  obj.foo("hello")

  define class myBaseclass as Custom
    procedure foo
      lparameters param1, param2
      if pcount() < 2
        param2 = "World"
      endif
      ? param1 + " " + param2
    endproc

  enddefine

  define class theAncestor as myBaseclass
    procedure foo
      lparameters param1, param2
      ? "I said:"
      ***dodefault(param1, param2) && pcount() in myBaseclass::foo PCOUNT() will always be 2!
      DO CASE
      CASE PCOUNT() = 1
          dodefault(param1)
      CASE PCOUNT() = 2
         dodefault(param1, param2)
      OTHERWISE
         dodefault()
      ENDCASE
   endproc
  enddefine


It's better to check with VARTYPE(param2). If the expected type of param2 is boolean you have to ensure that .F. is the default. -- Kurt
Bad idea. You cannot tell based on that check if optional parameter isn't passed or it has been passed with value .F.
If you cannot tell if the optional parameter isn't passed, and the default behaviour is to behave as though .F. has been passed, then it doesn't matter. Using PCount() like this excessively clutters code, and means subclassing a function with 5 parameters to perform one additional line of behaviour requires at least 15 lines of code. Furthermore, you cannot add additional optional parameters. In short, if you need to have three behaviours from a boolean value (not passed, passed as .T., passed as .F.), then you shouldn't be using a boolean value. -- Peter Crabtree
Thanks Peter. I wasn't clear enough. -- Kurt
If a subclass' method is overridden and includes a DODEFAULT that passes all possible parameters, the parent class' method will be unable to react to the value of PCOUNT in the overridden method. It's not necessarily a rookie mistake to use pcount in the parent class, although it may be a mistake to be unaware of this issue. There is a very simple way to preserve PCOUNT up the class hierarchy, without a complex DO CASE, allowing PCOUNT to be used in the parent classes.
LPARAMETERS m.tcParm1, m.tcParm2
*Here's a subclass' overridden method.
LOCAL m.lcParmList
m.lcParmList = PassParms("m.tcParm1, m.tcParm2",PCOUNT())
RETURN DODEFAULT(&lcParmlist.)

Now when the parent class' method fires, it will have as many parameters as were passed. I created something like this for Visual Max Frame Professional - x5PItems.PRG with more parameter checking. This is enough to demonstrate the concept:
*PassParms.PRG
LPARAMETERS m.tcParmlist,m.tnItems
RETURN LEFT(m.tcParmlist+",",AT(",",m.tcParmlist,m.tnItems)-1)
-- Mike Yearwood
This looks better than the CASE-thing, but it's still overhead and doesn't solve the problem, that you can't add additional optional parameters.
-- Kurt
Can't really argue there. I think you can add additional parameters in the lower classes. One alternative is to pass a parameter object. The parent class method would have to know what parameters to expect. The subclass could request a parameter object from another method or factory and you could check for parameter names. -- Mike Yearwood
There's repeated mention about the side-effect of the approaches that try to maintain PCOUNT()'s value when calling superclasses being the inability to add additional parameters in the superclass in the future... This inability also results from the PARAMETERS (or LPARAMETERS) statement itself! If a superclass' method has a new parameter added, all subclasses that want to support that parameter must have their parameter statement adjusted anyway. -- ?wgcs
Program opens and closes upon start
What you see is VFP opening and closing the program. In cases, when the program closes it does not release the memory because it did not run the code to do it and/or a reference in memory is still taken (garbage collection not done).
A Foxpro program needs a wait state. This is provided by VFP itself when you run in development environment (IDE). In an external (EXE) program you need to supply a READ EVENTS statement. This statement makes the program wait for user input, menu options, command buttons, etc. and react accordingly.
Usually, even when your main program resides in a form, you must supply a startring prg that contains the instantiation code for menus and forms, then issues READ EVENTS. The EXIT option (in a menu or a form's Button) issues a CLEAR EVENTS and program execution returns to the PRG after the line READ EVENTS where you proceed to do your cleanup. See also ON SHUTDOWN in help.
* main.prg
do menu mymenu
do form myform
READ EVENTS
* cleanup code here
return   && or quit

See also:
Forms Not Being Released From Memory
App Shut Down
One Form Vfp App
PRB: Menu Appears, Then Application Exits to Operating System
PRB:Visual FoxPro App's .EXE Quits & Returns to Command Window
PRB: Application Appears Then Exits to Operating System
Another cause of a form initiating and then disappearing is when .F. is returned from the INIT() of the form. -- Ben Creighton
Or LOAD(), both preventing the object from instantiating.

Refactored by Jim BoothOffsite link to http://www.jamesbooth.com
on Feb 5, 2002
See also VFP Misused And Abused VFP Gotchas
Category Learning VFP Category 3 Star Topics


( Topic last updated: 2012.03.13 11:24:06 AM )