Wiki Home

Scan End Scan


Namespace: VFP
SCAN [NOOPTIMIZE] [Scope] [FOR lExpression1] [WHILE lExpression2]
[Commands]
[LOOP]
[EXIT]
ENDSCAN

is a construct for looping through a set of records. The VFP 9 help states

SCAN ... ENDSCAN ensures that, upon reaching ENDSCAN, Visual FoxPro reselects the table that was current when the SCAN ... ENDSCAN loop began.

I remember seeing/hearing that some version of FP or VFP did not automatically / consistently reselect the table. Please identify this version or prove me wrong! -- Mike Yearwood

A Gotcha about using SCAN..ENDSCAN in a function is that no matter how you leave the SCAN loop, the select area you were scanning on is reselected.

This isn't a gotcha. This is how a scan works. It always reselects the work area it was in when the SCAN began. -- Craig Berntson

That is true for "a scan" (only one). But we are talking about nested scans. in this case, it is reselecting some other area than what it was when the SCAN began (assuming "the SCAN" refers to the one that the program is about to jump to.)

here is my version of the program:

create table tblA ( iFid1 i )
insert into tblA values (1)
insert into tblA values (2)
create table tblB ( iFid1 i )
insert into tblB values (1)
insert into tblB values (2)

?
select tblA
scan  && A
  ? "#1 A", alias()
  f()
  ? "#2 A", alias()
endscan && A
? "#3 A", alias()

function f
select tblB
scan && B
  select tblA
  ? "#4 A", alias()
  return
endscan && B


The "A" in ? "#x A" is the table that I would expect to be selected. I am surprised that #2 prints tblB.

The "ENDSCAN &&A" does NOT select tblA, it selects tblB. but it does know where to loop back to (SCAN && A).

OK, now that i really look at this in the debugger, I see something really weird: tblA gets selected by the RETURN!!! then tblB gets selected by ENDSCAN &&A

Apparently, how ever you end the scan (endscan, exit, or return), it selects the inital area. Are there any other ways to exit a scan?

-- Carl Karsten


Hmmm, this brings forward some very old issues of good program design principles. "One way in and one way out." comes to mind, also "Leave things as the function found them". Below is my revision of Function F, of course the logic of the llRet value may be changed depending on the desired result. This modification leaves no question as to the selected alias when the function returns, it will always be the same alias as when the function started. -- Jim BoothOffsite link to http://www.jamesbooth.com

function f
LOCAL lcAlias, llRet
lcAlias = ALIAS()
select tblB
scan && B
  select tblA
  ? "#4 A", alias()
  IF < Condition >
    llRet = .T.
    EXIT
  ENDIF
endscan && B
IF NOT EMPTY( lcAlias )
   SELECT ( lcAlias )
ELSE
   SELECT 0
ENDIF
RETURN llRet


Jim BoothOffsite link to http://www.jamesbooth.com
is correct. The called function should reset the environment. I prefer to use objects for this because they can reset thing when destroyed. -- Mike Yearwood
Not only is this by design but it's really the desirable behavior when you think about it. Otherwise if you select a different work area inside a SCAN and forget to re-select the original before reaching ENDSCAN you're going to get weird results.

If you need to bail out of a SCAN before it's done, set an indicator and EXIT the SCAN. Then, outside the SCAN, take whatever action is necessary based on the status of the indicator. For example:

llSuccess = .T.   && success indicator
SCAN
  lnCnt = lnCnt + 1
  ? "> > > Record "+alltrim(Str(RecNo()))+" y="+y
  if ErrorChecker(lnCnt)
     llSuccess = .F.   && set success indicator .F. and exit the scan
     exit
  endif
ENDSCAN
if !llSuccess   && if there was an error,
   * do whatever you need to do, like select another work area
endif
RETURN llSuccess
-- Rick Borup

This is a valid way to work around the designed behavior, but it becomes more hairy if the success/failure is detected inside nested structures (which, in my case, I had a Validation SCAN... It's purpose is to decide whether an action is allowed to happen. I want it to continue looking at all records until it finishes that everything is fine, but if it finds ANY problem, I want to get out as quickly as possible (it's a bottleneck in the algorithm). Some of the places are just in-line in the SCAN, others are inside FOR-loops in the scan, others in While loops, etc. A simple EXIT isn't guaranteed to get me all the way out of the scan; So I just wanted a RETURN out of the middle wherever... But was perpetually in the wrong select area.

If this is what you want to do, you're probably better off using a DO WHILE NOT EOF()/SKIP/ENDDO. -- Craig Berntson


Example:
oSel = Select()
SELECT ThisAlias
SCAN
  if ThisAlias.FieldToCheck = 'BAD VALUE'
    RETURN 'VALUE WAS BAD'
  endif
  for I = 1 to Max
    if NOT CheckIt( I )
      RETURN 'FAILED CHECK'
    endif
  endfor
  do while BadCnt < MaxBads
    if CheckBad( ThisAlias.FieldToCheck )
      RETURN 'TOO MANY BADS'
    endif
  enddo
ENDSCAN
SELECT (oSel)
RETURN 'GOOD!'


This code above would need to check a flag that was set after ENDFOR and after ENDDO (since an EXIT inside either would not leave the SCAN)

Another simple way to work around the SCAN behavior is to use a wrapper function that would save the old alias, etc, and restore the environment. The reason I don't like that idea is it'll junk-up my classes with functional methods (what to call these?) and Wrapper methods.
- ?wgcs

Try it this way -- RB
oSel = Select()
local lnError
lnError = 0   && lnError = 0 means no errors
SELECT ThisAlias
SCAN
  if ThisAlias.FieldToCheck = 'BAD VALUE'
    *RETURN 'VALUE WAS BAD'
    lnError = 1
    EXIT   && exits the SCAN
  endif
  for I = 1 to Max
    if NOT CheckIt( I )
       *RETURN 'FAILED CHECK'
       lnError = 2
       EXIT   && exits the FOR loop
    endif
  endfor
  do while BadCnt < MaxBads and lnError = 0
    if CheckBad( ThisAlias.FieldToCheck )
       *RETURN 'TOO MANY BADS'
       lnError = 3   && No need to EXIT since lnError <> 0 now.
    endif
  enddo
ENDSCAN
SELECT (oSel)
*RETURN 'GOOD!'
RETURN lnError   && Or return the corresponding message



In other words, if in a function you save the previous select area, then change it to a different area and SCAN on this second area, and encounter a reason in the middle of the SCAN to RETURN out of the function (but ARE careful to SELECT( oArea ) the original area first) The SECOND AREA (not the original area) is still selected when you get back to the calling code.

See the sample code below if this explanation isn't clear enough.
* Test if the last select area is reselected on ENDSCAN if RETURN is used to leave scan
create cursor a ( x c(5) )
create cursor b ( y c(5) )
for lnI = 1 to 100
  insert into a ( x ) values ( alltrim(str(lnI)) )
  insert into b ( y ) values ( PadL(alltrim(str(lnI)),5,'b') )
endfor


select A
?'> SELECT A'
?'> The select area is now '+alltrim(str(select()))+'  alias()='+alias()
?'> Calling Function "whatever"'
Do Whatever
?'> Back from "DO Whatever"... The select area SHOULD be back to "A"'
?'> The select area is now '+alltrim(str(select()))+'  alias()='+alias()



FUNCTION Whatever
PRIVATE oSel
LOCAL lnCnt
oSel = Select()
SELECT B
?'> > SELECT B'
?"> > Processing Select() area "+alltrim(str(select()))+"  Alias()="+alias()
lnCnt = 0
SCAN
  lnCnt = lnCnt + 1
  ? "> > > Record "+alltrim(Str(RecNo()))+"  y="+y
  if ErrorChecker(lnCnt)
    SELECT (oSel)
    RETURN .F.
  endif
ENDSCAN
Select A
RETURN .T.



FUNCTION ErrorChecker
PARAMETER cnt
if Cnt>10
  ? "> > > > Error! Must Exit procedure Whatever!"
  ?'> > > > SELECT (oSel)    [ oSel= '+alltrim(str(oSel))+'  alias(oSel)='+alias(oSel)+']'
  SELECT (oSel)
  RETURN .T.
endif
RETURN .F.

Contributors:
Category VFP Commands Category Code Samples
( Topic last updated: 2006.06.06 11:52:45 AM )