Welcome to Foxite.COM Community Weblog Sign in | Join | Help

Using a "Safe Select" to preserve your grid

A common problem when working with local cursors in VFP is that when they are used as the record source for a grid re-querying the cursor causes the grid to lose some, or all, of its settings. The reason that this happens is that whenever you execute a SQL Select statement in VFP, any existing target cursor gets closed, deleted and re-created. Since the underlying data source to which the grid is bound just got closed and deleted the grid loses its binding.

This is easily demonstrable, consider the following:

*** Run a basic select into a cursor

SELECT * FROM account INTO CURSOR JUNK NOFILTER

? DBF( ‘junk’ ) && Returns F:\TEMP\00002PL5008K.TMP

*** Repeat the query

SELECT * FROM account INTO CURSOR JUNK NOFILTER

? DBF( ‘junk’ ) && Returns F:\TEMP\00002PL500AQ.TMP

As you can see, the second run of the query creates a new cursor that is opened with the same alias as the first. A quick check on disk will show that the first cursor has indeed been deleted. The consequence, for the grid is that it has to re-build itself from scratch. It does this by deleting all of the existing columns and creating new columns for the new record source. The result is, of course, that all column specific settings are lost and, if your grid used custom columns, or controls they are all replaced by base classes (or, since the introduction of member classes, whatever member class was originally specified). However, any instance specific settings are lost and have to be re-created. This is not a good scenario!

One solution to this problem that I often see offered in on-line forum discussions is to set the grid’s RecordSource to an empty string before querying the cursor and restore it afterwards, like this:

*** Save the recordsource and blank it out

WITH ThisForm.Grid

  lcBoundTo = .RecordSource

  .RecordSource = ‘’

  *** Run the new query here

  SELECT <fields> FROM <table> INTO CURSOR <recordsource>

  .RecordSource = lcBoundTo

ENDWITH

and this works fine, providing that the grid displays columns in natural order!

The reason for the proviso is that what actually happens in this scenario is that when you clear the record source for the grid, the control sources for each column are also cleared. When the grid re-binds to the new alias it no longer has specific control sources for its columns and the result is that the grid now displays the data based on its position in the record source. The first column shows column 1 of the record source, the second displays column 2 and so on.

Typically what happens next is that the questioner goes off, tries the solution, finds that their grid is now completely messed up and comes back with a “what do we do now?”. The next stage is usually to suggest that they change their entire methodology and replace the cursor with a parameterized view. This is perfectly valid and a parameterized view will work just fine in this scenario.

However, parameterized views have one serious limitation. Since views must be pre-defined, it is not easy to create a view that will accept an ad-hoc filter condition and it is usually this requirement that has driven the person to use a cursor in the first place. For example, suppose that in a customer enquiry screen the requirement is to allow a user to specify any combination of First Name, LastName, Social Security Number, City, State, Order Number. Constructing a parameterized view to handle this would be difficult, if not actually impossible.

A solution which addresses all of the issues is a technique called a ‘safe select’.

The idea is that since cursors are always created on the local user’s workstation, and are always opened exclusive, we can use the ZAP command to clear, without closing, a cursor. Then, instead of running a query directly into the working cursor we use an intermediate (or ‘dummy’) cursor as the target for the query and simply append the results from that into the working cursor. Here is the code for a ‘safe select’

*** Create the working cursor

SELECT * FROM account WHERE 0 = 1 INTO CURSOR curacct READWRITE

*** Now run the real query

SELECT * FROM account WHERE name LIKE ‘Sm%’ INTO CURSOR curdummy

*** Clear the working cursor and append the results

SELECT curacct

ZAP IN curacct

APPEND FROM DBF( ‘curdummy’ )

USE IN curDummy

Since the working cursor is never closed, there is no effect on any control (including grids) which is using it as a data source. This technique allows us to maintain the flexibility of a cursor without the undesirable side effects caused by the repeated opening and closing as it is re-queried. Of course if you had to write this code every time you want to query a cursor it could get a little tedious, but we have a fully object oriented environment in VFP, why not just add a method to your root form class which accepts two parameters - the query string and the name of the target cursor. Then the code is nicely encapsulated and is available to any form that needs it.

You can even augment the code so that if the target alias does not exist, it is created by the method. Here is my basic 'SafeSelect' method which expects to receive a query string that does NOT include the INTO clause - that is added here:

LPARAMETERS tcSql, tcAlias
LOCAL lnSelect, lcSql

*** Preserve Work Area
lnSelect = SELECT(0)

*** Cursor does not exist
IF NOT USED( tcAlias )
  *** Create it directly
  lcSql = tcSql + " INTO CURSOR " + tcAlias + " READWRITE"
  &lcSql
ELSE
  *** Cursor does exist, use a safe select here
  lcSql = tcSql + " INTO CURSOR curdummy"
  &lcSql
  *** Clear and Update the woirking cursor
  SELECT (tcAlias)
  ZAP IN (tcAlias)
  APPEND FROM DBF( 'curdummy' )
  USE IN curdummy
ENDIF

*** Restore work area and return status
SELECT (lnSelect)
RETURN USED( tcAlias )

As for the READWRITE clause if you are still using a version of VFP that does not support this, there is a way to do it, but I am not going to tell you because you should have UPGRADED. There is no reason not to and a lot of very good reasons why you should - the main one being to encourage Microsoft to keep developing VFP for example....

Published Saturday, March 19, 2005 2:03 PM by andykr

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Using a "Safe Select" to preserve your grid

Saturday, March 19, 2005 4:55 PM by Bernard Bout
No why didn't I think of that.

Really good idea that I will use.

# re: Using a "Safe Select" to preserve your grid

Saturday, March 19, 2005 5:23 PM by Andy Kramek
This is what I want to do with this blog. I will try and post something like this at least once a week (until I run out of stuff)

# re: Using a "Safe Select" to preserve your grid

Saturday, March 19, 2005 10:59 PM by Jamie Osborn
Thanks for that . I actually do this (now using INSERT INTO myalias SELECT * FROM mytempalias).

I was always annoyed that clearing the recordsource in the grid and reselecting only worked if the cursor was in exactly the right order but you have confirmed to me that Fox does indeed clear the controlsource for each column.

# re: Using a "Safe Select" to preserve your grid

Sunday, March 20, 2005 12:02 AM by Andy Kramek
>> now using INSERT INTO myalias SELECT * FROM mytempalias

Yes, I had considered that approach too but rejected it on the grounds that it is too version-specific for a set of generalized classes. Not an issue when you control the version of the software, but definitely an issue if someone using VFP 5.0 or 6.0 tries to use your code!

# re: Using a "Safe Select" to preserve your grid

Monday, March 28, 2005 6:32 PM by Juan Carlos Garcia(Rep. Dom.)
I prefered to do it in another way.

My base class has two method called:

SaveGridProp()
RestoreGridProp()

with the following code

procedure SaveGridProp
lparameter toGridObj
local loCol
toGridObj.tag = toGridObj.recordSource
for loCol in toGridObj.columns
loCol.tag = loCol.controlSource
endfor
toGridObj.recordsource = ''
endproc

procedure RestoreGridProp
lparameter toGridObj
local loCol
toGridObj.recordSource = toGridObj.tag
for loCol in toGridObj.columns
loCol.controlSource = loCol.tag
endfor
endproc

then i code like this, when i want to prevent to kill my GridObject when i requery new data:

*----
thisform.SaveGridProp(thisform.mygrid)

*-- Execute SELECT - SQL for retrieve new
*-- data to my Grid

SELECT * FROM AnyTable INTO AnyCursor

thisform.RestoreGridProp(thisform.myGrid)
*------
ps:
Excuseme about my english. :D

# re: Using a "Safe Select" to preserve your grid

Monday, March 28, 2005 7:27 PM by Andy Kramek
Yes, but all that does is to preserve the controlsource. What about other properties, or instance level code that will be lost when the grid rebuilds itself using base class columns?
This ONLY works safely if you never have any custom code in your grid.

# re: Using a "Safe Select" to preserve your grid

Tuesday, March 29, 2005 4:25 AM by Lucy
I usually has a method for setting grid's properties based on the way i want. Then whenever i have to set the grid's recordsource to empty then do a requery. The method is called afterwards to set the grid's properties back to its original settings. Is this a good way Andy?

# re: Using a "Safe Select" to preserve your grid

Tuesday, March 29, 2005 5:33 AM by Andy Kramek
If it works for you, Lucy that's fine. However, it's not the way I would do it, I prefer the safe select approach described here because it avoids the necessity to keep saving and restoring a lot of settings.

# re: Using a "Safe Select" to preserve your grid

Wednesday, March 30, 2005 2:34 AM by Lucy
Ok. But i will apply the safe select approach next time i go coding. I think this is faster and with lesser codes. :-) thanks for sharing...

# re: Using a "Safe Select" to preserve your grid

Wednesday, March 30, 2005 3:49 AM by Andy Kramek
My pleasure, Lucy. As I said, if I can helpl someone to avoid the mistakes I made, it's worth while!

# re: Using a "Safe Select" to preserve your grid

Tuesday, April 12, 2005 7:19 PM by Dennis Longfellow
I do like this approach and like Lucy will use it in the future.

I noticed what I think may be a typing error in your SafeSelect Method:

The two lines that begin with lcSQL = lcSQL + ... I think should be lcSQL = tcSql + ...

Thanks for sharing.

# re: Using a "Safe Select" to preserve your grid

Tuesday, April 12, 2005 8:01 PM by Andy Kramek
Whoops! You are correct, Dennis, thank you for spotting the typo. I have amended the code accordingly.

# re: Using a "Safe Select" to preserve your grid

Monday, December 05, 2005 11:26 PM by anabisbe@amby.net
Thanks Andy !!

You can find the Spanish version of this article at (Puede encontrar la versión en Español de este artículo en:)

http://www.portalfox.com/modules.php?op=modload&name=Sections&file=index&req=viewarticle&artid=69

Regards / Saludos,

Ana
www.amby.net
www.PortalFox.com

# re: Using a "Safe Select" to preserve your grid

Wednesday, December 14, 2005 10:05 PM by Sarosh
How should one go about implementing the "Safe Select" while using grids and CursorAdapters?

# re: Using a &quot;Safe Select&quot; to preserve your grid

Tuesday, June 19, 2007 1:10 PM by Siva
I have used 'SafeSelect' common method, itz working fine. More thanks to Andy Kramek.

What do you think?

(required) 
required 
(required)