I'll spare you the daily details of a series of months that flew past in some sort
of blur that I can only describe as exhausting, surreal and extremely fast. For keystroke's
sake, "Busy, Busy, Busy" says it all. That having been said, even though family emergencies
and pressing deadlines are not yet a week in the grave, I feel good.
What's up with VFP Studio?

It is completely my fault (lack of communication) that this question is yet being
asked. With the VFP Language Service finished, Bo Durban and I were headed directly
into the Visual Studio designers (the next things we were creating for VFP Studio).
We then found out that the designers were being revamped in VS2010 (not like upgraded...
I mean completely revamped) which meant that we would have a suspect upgrade path
for anything we created in VSX prior to VS2010. That, added to the already super-intense
world of Microsoft's VSX that Bo and I were living in, and both of us being buried
in work, convinced both of us to shelve VFP Studio for awhile.

Other interesting or not so interesting VFP Studio related Fast Facts: I am not opposed
to diving back in to it (VSX and VFP are very cool). I am still on the VSX-Insiders
list at Microsoft. I have other things to do and learn at the present moment.

There are some cool things I could post with this. But, then I'd sit here editing,
and re-editing, this post until it was the way I wanted it and the example, FLL, or
whatever I posted met my cool-enough-to-post checks. So, for now, I think I'll just
post this short entry. If nothing else, it should prove whether this "Post to Weblog"
button still works.

Added ability to specify padding, key size, block size, and initialization vector
(IV) where applicable (these changes allow compatibility with .NET and other encryption
systems as well as the ability to adhere specifically to the AES specification as
set out in the FIPS
197 Specification).

Added VFP test program files to the downloads that allow developers to see for themselves
that return values are correct for various functions provided by the FLL.

Added GenerateKey() function that will return a random key or IV based on some specified
rules (parameters).

Corrected and updated documentation (see below).

Please provide feedback here or send me an email if you run into any problems with
this latest version. You'll be able to tell from the test program files (Test Vectors)
that I did a lot of testing on my own to make sure that this version was solid. I
also did a number of tests between .NET System.Security.Cryptography classes (such
as RijndaelManaged for instance) and this FLL to ensure that there was a good deal
of compatibility.

The vfpencryption71.fll requires the VC++ 7.1 runtimes whereas the vfpencryption.fll
requires the VC++ 9.0 runtimes. If you are getting a "FLL is Invalid" error when running
either of these FLLs it is because you are missing the runtimes on the system you
are deploying your application on. What about the previous vfpencryption.fll that
used the VC++ 8.0 runtimes? I continue to update to the latest Visual Studio (10.0
will be next) and I suggest you do the same. Most VFP developers are using the vfpencryption71.fll
as the C runtime matches the one used for VFP 9.0 (msvcr71.dll) and it provides the
exact same functionality as vfpencryption.fll. However, I have had numerous requests
for an updated build of the FLL using the latest Visual Studio, so I include it below.

cStringtoEncrypt - A plain text string that you want
to have encrypted, such as "Hello World!"

cSecretKey - A plain text string that is the Key you
want used during encryption, such as "My_SeCrEt_KeY".
Please note that keys may need to be of a particular length for certain types of encryption.
Refer below for more information.

nEncryptionType - There are currently 5 types of encryption
available. The value of this parameter determines that type of encryption used and
how long your Secret Key should be. A single character in Visual FoxPro is equal to
1 byte or 8 bits. So an encryption algorithm requiring a 128-bit key would need a
Secret Key of 16 characters (16 x 8 = 128).

nEncryptionMode - There are three different modes available
for the each of the encryption types listed above. They include: Electronic Code Book
(ECB), Cipher Block Chaining (CBC), Cipher Feedback Block (CFB), and Output Feedback
Block. The nEncryptionMode parameter does not apply to RC4 encryption
(nEncryptionType = 1024).

0 = ECB *Default
1 = CBC
2 = CFB
3 = OFB

nPaddingMode - For Block Ciphers the cStringtoEncrypt
is padded to a multiple of the block size for the algorithm. Setting this parameter
allows you to specify how this padding is done.

cIV - The Initialization Vector (IV) that should be used
for CBC, CFB, and OFB modes should use. This IV should match the specified nBlockSize
in total bytes (characters).

Return Value:

Character data type - the encrypted form of cStringtoEncrypt.

Remarks:

When saving the return value of Encrypt() function to a field in a table, remember
that Visual FoxPro will append blanks to the end of the string in order to fill the
character field to its designated length. This can cause problems when decrypting
the data as the spaces will be considered part of the encrypted string. To work around
this, I suggest placing a single CHR(0) at the end of the encrypted string when saving
it to the table. Then when decrypting the data just the portion prior to the CHR(0)
can be sent into the Decrypt() function. This does not apply
when using RC4 encryption (nEncryptionType = 1024).

cEncryptedString - A string that has been encrypted using
the Encrypt() function.

cSecretKey - A plain text string that is the same Key
that you used when you encrypted the data using the Encrypt function, such as "My_SeCrEt_KeY".
Please note that keys may need to be of a particular length for certain types of decryption.
Refer below for more information.

nDecryptionType - There are currently 5 types of decryption
available and they correspond to the same ones available in Encrypt(). A single character
in Visual FoxPro is equal to 1 byte or 8 bits. So an decryption algorithm requiring
a 128-bit key would need a Secret Key of 16 characters (16 x 8 = 128).

nDecryptionMode - There are three different modes available
for the each of the encryption types listed above. They include: Electronic Code Book
(ECB), Cipher Block Chaining (CBC), Cipher Feedback Block (CFB), and Output Feedback
Block. The nDecryptionMode parameter does not apply to RC4 decryption
(nDecryptionType = 1024).

0 = ECB *Default
1 = CBC
2 = CFB
3 = OFB

nPaddingMode - For Block Ciphers the cStringtoEncrypt
is padded to a multiple of the block size for the algorithm. Setting this parameter
allows you to specify how this padding is done.

cIV - The Initialization Vector (IV) that should be used
for CBC, CFB, and OFB modes should use. This IV should match the specified nBlockSize
in total bytes (characters).

Return Value:

Character data type - the decrypted form of cEncryptedString followed by a variable
number of CHR(0)s. See Remarks below for further clarification

Remarks:

IMPORTANT: Decryption is done on blocks of memory, so
when the decrypt function returns the encrypted string it will be followed by a variable
number of CHR(0)s unless the decrypted string just happens to end at exactly the same
location as the last block decrypted. These extraneous CHR(0)'s can be removed using
a number of Visual FoxPro functions, such as STRTRAN(), CHRTRAN(), or a combination
of LEFT() and AT(). This does not apply when using RC4 decryption
(nDecryptionType = 1024).

cFiletoEncrypt - A plain text string that is the fullpath
to the file you wish to be encrypted, such as "C:\SensitiveInfo.doc"

cDestinationFile - A plain text string that is the fullpath
to an encrypted file you wish to have created on disk, such as "C:\EncryptedInfo.doc".
If this file doesn't exist then it will be created for you.

cSecretKey - A plain text string that is the Key you
want used during encryption, such as "My_SeCrEt_KeY".
Please note that keys may need to be of a particular length for certain types of encryption.
Refer below for more information.

nEncryptionType - There are currently 5 types of encryption
available. The value of this parameter determines that type of encryption used and
how long your Secret Key should be. A single character in Visual FoxPro is equal to
1 byte or 8 bits. So an encryption algorithm requiring a 128-bit key would need a
Secret Key of 16 characters (16 x 8 = 128).

nEncryptionMode - There are three different modes available
for the each of the encryption types listed above. They include: Electronic Code Book
(ECB), Cipher Block Chaining (CBC), Cipher Feedback Block (CFB), and Output Feedback
Block. This does not apply when using RC4 encryption (nEncryptionType
= 1024).

0 = ECB *Default
1 = CBC
2 = CFB
3 = OFB

nPaddingMode - For Block Ciphers the cStringtoEncrypt
is padded to a multiple of the block size for the algorithm. Setting this parameter
allows you to specify how this padding is done.

cIV - The Initialization Vector (IV) that should be used
for CBC, CFB, and OFB modes should use. This IV should match the specified nBlockSize
in total bytes (characters).

Return Value:

None

Remarks:

Currently the cFiletoEncrypt and cDestinationFile parameters cannot point to the same
file. This may be revised in a future version. But for safety sake, this function
requires that the original file be left untouched.

cEncyptedFile - A plain text string that is the fullpath
to the file you wish to be decrypted, such as "C:\EncryptedInfo.doc"

cDestinationFile - A plain text string that is the fullpath
to a decrypted file you wish to have created on disk, such as "C:\SensitiveInfo.doc".
If this file doesn't exist then it will be created for you.

cSecretKey - A plain text string that is the same Key
that you used when you encrypted the data using the Encrypt function, such as "My_SeCrEt_KeY".
Please note that keys may need to be of a particular length for certain types of decryption.
Refer below for more information.

nDecryptionType - There are currently 5 types of decryption
available and they correspond to the same ones available in Encrypt(). A single character
in Visual FoxPro is equal to 1 byte or 8 bits. So an decryption algorithm requiring
a 128-bit key would need a Secret Key of 16 characters (16 x 8 = 128).

nDecryptionMode - There are three different modes available
for the each of the encryption types listed above. They include: Electronic Code Book
(ECB), Cipher Block Chaining (CBC), Cipher Feedback Block (CFB), and Output Feedback
Block. This does not apply when using RC4 decryption (nDecryptionType
= 1024).

0 = ECB *Default
1 = CBC
2 = CFB
3 = OFB

nPaddingMode - For Block Ciphers the cStringtoEncrypt
is padded to a multiple of the block size for the algorithm. Setting this parameter
allows you to specify how this padding is done.

The hash is returned as a series of binary characters. However, it is more common
to see hashes in a hexBinary format. This can be accomplished in Visual FoxPro by
taking the return of the Hash() function and sending it in as a parameter to the STRCONV()
function. For example:

?STRCONV(Hash("Some String"), 15) && hexBinary Hash

Function HASHFILE()

Signature: HashFile(cFileName[, nHashType])

Parameters:

cFileName - The fullpath and name
of an existing file you wish to generate a message digest for

nHashType - The type of hash function to generate. There
are currently 7 different hash functions supported

The hash is returned as a series of binary characters. However, it is more common
to see hashes in a hexBinary format. This can be accomplished in Visual FoxPro by
taking the return of the HashFile() function and sending it in as a parameter to the
STRCONV() function. For example:

?STRCONV(HashFile("C:\MyFile.txt"), 15) && hexBinary Hash

Function HASHRECORD()

Signature: HashRecord(cAlias[, nHashType[,lIncludeMemos]])

Parameters:

cAlias - The table alias containing
the record to be hashed

nHashType - The type of hash function to generate. There
are currently 7 different hash functions supported

The hash is returned as a series of binary characters. However, it is more common
to see hashes in a hexBinary format. This can be accomplished in Visual FoxPro by
taking the return of the HashRecord() function and sending it in as a parameter to
the STRCONV() function. For example:

Binary Character Data - the HMAC for the given cStringtoHash and cSecretKey.

Remarks:

The HMAC is returned as a series of binary characters. However, it is more common
to see HMACs in a hexBinary format. This can be accomplished in Visual FoxPro by taking
the return of the HMAC() function and sending it in as a parameter to STRCONV(cReturn,
15).

Function CRC()

Signature: CRC(cExpression[, nCRCType])

Parameters:

cExpression - The string you wish
to have a CRC generated for

nCRCType - The type of CRC to generate. There are currently
2 different CRC types supported

1 = 16-bit
2 = 32-bitReturn Value:

Numeric Data - the CRC for cExpression.

Remarks:

The CRC that is returned is unsigned, which means that the returned 16-bit CRC needs
to be treated as a 4 Byte numeric value and the 32-bit CRC as a 8 byte numeric value
in Visual FoxPro. The operation of the CRC() function presented here is quite similar
to Visual FoxPro's Sys(2007) function, however you will find that creation of 32-bit
CRCs is much faster using this function.

Function CRCFILE()

Signature: CRCFile(cFileName[, nCRCType])

Parameters:

cFileName - The fullpath and name
of an existing file you wish to generate a CRC for

nCRCType - The type of CRC to generate. There are currently
2 different CRC types supported

1 = 16-bit
2 = 32-bitReturn Value:

Numeric Data - the CRC for cFileName.

Remarks:

The CRC that is returned is unsigned, which means that the returned 16-bit CRC needs
to be treated as a 4 Byte numeric value and the 32-bit CRC as a 8 byte numeric value
in Visual FoxPro.

Function CRCRECORD()

Signature: CRCRecord(cAlias[, nCRCType[,lIncludeMemos]])

Parameters:

cAlias - The table alias containing
the record to be hashed

nCRCType - The type of CRC to generate. There are currently
2 different CRC types supported

The CRC that is returned is unsigned, which means that the returned 16-bit CRC needs
to be treated as a 4 Byte numeric value and the 32-bit CRC as a 8 byte numeric value
in Visual FoxPro. The operation of the CRC() function presented here is quite similar
to Visual FoxPro's Sys(2017) function, however you will find that this CRC function
is faster than Visual FoxPro's Sys(2017). Also, this function allows you to specify
a table alias, which allows CRCs to be created for a record in a table other than
the currently selected work area. On the downside, this function does not allow you
to specify a comma delimited list of fields to exclude like Sys(2017) does.

lIncludeUpper - Flag
determining whether uppercase characters should be included when generating the key.
.T. = Include Uppercase Characters, .F. = Exclude Uppercase
Characters (default)lIncludeSpecial - Flag
determining whether Special Characters (characters "{}|\\]?[\":;'><,./~!@#$%^&*()_+`-=")
should be included when generating the key. .T. = Include Special Characters, .F.
= Exclude Special Characters(default)

Return Value:

Character Data - the random key generated based on the specified rules.

Remarks:

The key generated is a random set of lowercase characters by default. To add additional
possible characters for generating the key you can use the parameters (2-4) as specified.
The random keys returned can be used for the other FLL functions that allow for a
cSecretKey or cIV. This function is provided as a convenience function for developers
needing to produce a random key quickly and easily.

Apply Application Manifest at Compile Time with Projecthookhttp://www.sweetpotatosoftware.com/spsblog/PermaLink,guid,ca7afa55-24a9-46e8-bd3c-99df6909528b.aspx2009-08-03T06:50:30.633-05:002009-08-03T06:50:30.633505-05:00

Some Time Ago...

I wrote a blog entry, PE
Files, UAC, Reg-Free COM, and Other Crazy Stuff - Part 2, that detailed how to
view and edit the application manifest that is inserted into VFP compiled modules.
I also took some time to explain why someone would want to do such a thing. The top
two reasons most VFP developers want to do this is to elevate permissions under Window's User
Account Control (UAC) and to provide for Registration-Free
COM. I guess you may also find the need to edit or add strings to the PE file's
String Table resource (minority of VFP developers) which is provided for as well.

While the code and concepts detailed in that previous blog entry were advanced and
pretty decent (if i do say so myself), it suffered from a couple of bugs and most
VFP developers probably found it more than a little difficult to implement "out-of-the-box"
for their projects that needed elevated UAC permissions or Reg-Free COM. I attempt
to make things slightly more straight-forward and bug-free in this blog entry.

Enter the ProjectHook Class

There's plenty of good information on the ProjectHook
class online and in the VFP 9.0 SP2 help file (get the latest and greatest help
file out
on VFPX), so I won't go into a great amount of detail here. I simply wanted an
easy way to apply a new manifest to a VFP executable after I had built a project.
The ProjectHook class provides a BeforeBuild event where I can get the fullpath
of the EXE being built and an AfterBuild event which allows me to perform operations
immediately after the EXE is built. So, this class provided exactly what i needed
and made applying a modified manifest a breeze.

How to Use ProjectHookEx.vcx

In order to use the projecthookex.vcx (download provided at the bottom of this blog
entry), you simply need to unzip and copy it somewhere and then open up one of your
projects and go into the Project Information dialog (accessible either by right-clicking
the project or under the Project system menu... look for Project Info) and set the
Project Class property as shown in the following screen shot...

...then simply click OK, exit your project and reopen your project. The projecthook
isn't instantiated until you reopen the project for the first time after setting the
Project Class property.

The default settings for the hookex class are set so that the default VFP 9.0 SP2
manifest, which has its trustinfo set to asInvoker, is changed to requireAdministrator.
Take a look inside hookex's AfterBuild event for the code that accomplishes this (short
and sweet). You'll see a couple commented out lines of code in the AfterBuild event
that show how to accomplish Reg-Free COM and String Table resource edits as well.
In any event, once the Project Class is set for your project hookex will be instantiated
and ready to go any time you open and build your project. When the manifest is applied
it will be echoed to the screen so you can see it and ensure that it has been applied
(you can turn this off by changing the moduleresourceeditor class's showmanifestafterapplying property
if you so desire).

External Manifests Anyone?

For those of you that prefer to use an external manifest for your executables, you
can do so by setting the moduleresourceeditor class's externalmanifest property. The
cool thing about this is that the myproj.exe.manifest file is created in the same
directory as your executable and the executable's internal manifest is removed to
prevent it from interfering with your external manifest. Then you can edit the external
manifest using favorite XML editor or even notepad. This was one of the enhancements
I added to this that is not in the code that I posted in the original blog entry regarding
PE Assembly/Application Manifests.

What's Next

There are plenty of improvements and enhancements (automatically searching project
for all COM components and automatically adding them to the manifest for instance)
that could be made to this class and I hope that my readers will make suggestions
and even try their hand at coding a few. There are lots of good
uses for VFP's ProjectHook class. I'd love to hear from you and what you feel
needs to be done to improve this ProjectHook class. But, I'm cool if you just want
to read this, download the class library and use it. Until next time... VFP Rocks!

Added KeyForCursor property - this allows a JSON key to be specified for identifying
cursors (defaulted to "VFPData")

Enhanced Stringify and Parse methods - they now handle the VFP Collection class
with aplomb

Added KeyForItems property - when serializing Collections, the key for the
contained JSON object's array property can be defined (defaulted to "items")

Renamed DateSerializationType property - it is now ParseDateType

Enhanced Parse - JSON class will now (optionally) respect Class and ClassLibrary
properties if they exist in a JSON object being deserialized (see ParseRespectClass
property). This means that if you Stringify() a VFP Form and then Parse() the JSON
(with ParseRespectClass = .T.) you will get a Form object back rather than an Empty
object with the same properties as the Form.

I would consider the JSON class production-ready at this point. This doesn't mean
that bugs won't be found in the future, but it has been hammered on enough that I
wouldn't hesitate to use it in a production application.

Visual FoxPro Now ListedI got in touch with Douglas Crockford and
was able to get Visual FoxPro listed on JSON.org with
a link to my initial post. I appreciate Douglas Crockford taking the time out of his
busy schedule to list Visual FoxPro on the JSON site.

Special Thank YouNearly all of the fixes and enhancements provided in this version of the JSON
class were made possible by the help and prodding I received from Frank Dietrich of
Berlin. He is an extremely bright fellow. Implementing the Timezone and Daylight vs.
Standard stuff drove us both to the brink. I sincerely doubt I could have done it
without his help.

The
download contains the JSON class in both VCX and PRG format. They are the same. Use
whichever one you prefer.
This weblog is sponsored by SweetPotato
Software, Inc.

VFPCompression Update - A Couple of Fixes and a Slight Enhancementhttp://www.sweetpotatosoftware.com/spsblog/PermaLink,guid,416ae65b-0af2-4145-8637-aa0d81d77637.aspx2008-12-08T05:35:21.939-06:002008-12-08T05:35:21.9397376-06:00Craig Boyd

VFPCompression Bug Fixes
I received some really good feedback after posting my previous
blog entry concerning this library from Curt Hamlin and Eduard. There were two
bugs there that I felt needed some immediate attention, so in this blog entry I present
a version of VFPCompression that addresses them.

The first bug was the one that Curt Hamlin ran into where something screwy was going
on if the zip file wasn't somehow relative to the files or folders being zipped. The
following code...

...would result in an empty zip file being created (not good). While the other bug
was the one that Eduard reported where the ZipFolderQuick function wasn't applying
the optional password to the files being zipped. To make a long story short, I was
able to verify that these were actual bugs in the library, and I have uploaded
a new version of VFPCompression.fll that fixes them.

VFPCompression Enhancement
Something that I didn't like in the FLL was that in order to extract the contents
of a zip to a particular folder that folder had to already exist in the file system.
I decided that the FLL should try and create a folder if it doesn't already exist
when extracting, which it now does.

Search Revisited
I've spoken about the work I was doing with Windows
Desktop Search (WDS) and Visual FoxPro before on this blog here and here.
Though the project I present in this blog entry began when WDS 2.0 was the latest
and greatest, it was rewritten for WDS 3.0 and has been revamped some and tested with
WDS 4.0 (the latest and greatest as of this writing). In any event, this project was
being considered by Microsoft (MS) for inclusion as part of Sedna, but never
made the cut. I later obtained permission from MS to release it on my own, but haven't
found time to test it or post it until now.

Foxy Search Value
There are many things of value in the Foxy Search sample...

Shows how to properly use the WDS OLEDB Provider from VFP.

Provides a guide to the proper SQL syntax for WDS.

Includes a list of fields available via the OLEDB Provider. (see fields.dbf)

Provides VFP develpers a class for working with WDS and ultimately returning search
results as VFP cursors.

If nothing else, Foxy Search provides a pretty good sample for messing with when trying
out various aspects of WDS. It does not provide examples of the creation or use of IFilters,
handlers, or thumbnail extractors - if you are looking for those you'll have to look
elsewhere at the moment.

Foxy Search Errors
It should be noted that Foxy Search can, and will, throw errors depending on the combinations
of display fields, filters, and/or sorting chosen. Rather than attempt to restrict
what can and can't be chosen to lessen the errors, I felt it was easier to just allow
the developer using it to mess around with everything and see what is and what isn't
possible. Feel free to change the Throw Error in the Catch of the form's GetSearchResults
method to handle the various errors you run into if you feel like it (or just comment
it out if you want it to fail silently).

Don't Have Windows Desktop Search?You can check whether you have Windows Desktop Search and what version of
it is on your system by looking in the Control Panel's Add & Remove Programs or
by looking in your registry under the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
Desktop Search" key where there is a "Version" value. If you don't have WDS (or you
are running an outdated version) you can go to the MS site to download
and install WDS v4.0.

VFPCompression UpdateThis update
contains a fix that effects the Unzip routines and a new function called ZipComment().
I've also updated the sample code and documentation that is provided with this library.

The bug that was fixed
pertained to 0 byte (length) files were being ignored when a zip file's contents were
extracted. The Zip routines were handling the 0 byte length files correctly.

The ZipComment() function
allows you to add, replace or delete the global zipfile comment for a specified zip
file. The code at the bottom of this blog entry shows an example of its use and the
documentation now includes this function.

Special Thank YouSpecial thanks goes out to Mike Sue-Ping who posted out on the Universal
Thread about the bug that is fixed in this update.

What's NextI still plan to add more functionality to this FLL (ability to create cabs
and self-extracting archives, better error handling, etc.). I'm hopeful that those
of you that download this library will take the time to provide me feedback and any
suggestions you may have for further improving it. If you do find a bug, please comment
on it here and/or contact me directly. Note the "Contact Craig Boyd" link to the right
of this blog entry.

nLevel - The compression level to use which is 1 through 9 (1 is the
fastest, while 9 is the best compression). The default value for this parameter is
6.

Return Value:

Character Data - the compressed version of cString.

Remarks:

This function is particularly useful in a client-server application
given that strings of data (such as memo fields, character fields, and xml) can be
compressed before sending them across the network and then extracted at the other
end (using UnzipString).

Function ZipFileQuick()

Signature: ZipFileQuick(cFileName[,
cPassword])

Parameters:

cFileName - The fully
qualified file name (full path) of the file you wish to have compressed.

cPassword - The password
you wish to protect the zipped file with.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

The zip file that this
function creates will have the same file name as cFileName, with the extension as
".zip".

Function ZipFolderQuick()

Signature: ZipFolderQuick(cFolderName[,
lIgnorePaths[, cPassword]])

Parameters:

cFolderName - The full
path to the folder you wish to have zipped.

cPassword - The password
you wish to protect the zipped files with.

lIgnorePath - If you wish
to ignore the relative path of the file into the zip file that is being created
you would pass .T. for this parameter. The default value for this parameter is .F.
which means that the relative path will be respected.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

The zipfile that this
function creates will have the same file name as cFolderName, with the extension as
".zip".

Function ZipOpen()

Signature: ZipOpen(cZipFileName[,cFolderName[,lAppend]])

Parameters:

cZipFileName - The file
name or full path of the zip file you wish to create.

cFolderName - The full
path of the folder in which you want cZipFileName created.

lAppend - If the zip file
you are compressing to exists you can choose to append to it by passing .T. to this
parameter. Defaults to .F..

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

ZipOpen() is used in conjunction
with the matching ZipClose(). The usual series of function calls would consist of
creating/opening the zip file using ZipOpen, zipping files and/or folders using ZipFile/ZipFileRelative/ZipFolder,
and then closing the zip file using ZipClose.

Function ZipClose()

Signature: ZipClose()

Parameters: None

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

ZipClose() must be called
after issuing a ZipOpen(). The usual series of function calls would consist of creating/opening
the zip file using ZipOpen, zipping files and/or folders using ZipFile/ZipFileRelative/ZipFolder,
and then closing the zip file using ZipClose.

Function ZipFile()

Signature: ZipFile(cFileName[,lIgnorePath[,cPassword]])

Parameters:

cFileName - The file name
or full path of the file you wish to compress.

lIgnorePath - If you wish
to ignore the relative path of the file into the zip file that is being created
you would pass .T. for this parameter. The default value for this parameter is .F.
which means that the relative path will be respected.

cPassword - The password
you wish to protect the zipped file with.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

ZipFile() is used between
calls to ZipOpen() and ZipClose(). The usual series of function calls would consist
of creating/opening the zip file using ZipOpen, zipping files and/or folders using
ZipFile/ZipFileRelative/ZipFolder, and then closing the zip file using ZipClose.

The cPassword is usually the same for all files within a zip, however it does not
need to be the same. Different passwords can be specified for different files and
you can even selectively password protect files within the zip.

Function ZipFileRelative()

Signature: ZipFileRelative(cFileName[,cRelativePath[,
cPassword]])

Parameters:

cFileName - The file name
or full path of the file you wish to compress.

cRelativePath - The relative
path you wish to have saved in the zip for this file. This allows you to set up the
structure (relative paths) in the zip different from the actual relative paths of
the files you are compressing.

cPassword - The password
you wish to protect the zipped file with.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

ZipFileRelative() is used
between calls to ZipOpen() and ZipClose(). The usual series of function calls would
consist of creating/opening the zip file using ZipOpen, zipping files and/or folders
using ZipFile/ZipFileRelative/ZipFolder, and then closing the zip file using ZipClose.

The cPassword is usually
the same for all files within a zip, however it does not need to be the same. Different
passwords can be specified for different files and you can even selectively password
protect files within the zip.

Function ZipFolder()

Signature: ZipFolder(cFolderName[,lIgnorePaths[,
cPassword]])

Parameters:

cFolderName - The full
path to the folder you wish to compress.

lIgnorePaths - If you
wish to ignore the relative path of the folder into the zip file that is being created
you would pass .T. for this parameter. The default value for this parameter is .F.
which means that paths will be respected.

cPassword - The password
you wish to protect the zipped file with.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

ZipFolder() is used between
calls to ZipOpen() and ZipClose(). The usual series of function calls would consist
of creating/opening the zip file using ZipOpen, zipping files and/or folders using
ZipFile/ZipFileRelative/ZipFolder, and then closing the zip file using ZipClose.

Function UnzipString()

Signature: UnzipString(cString)

Parameters:

cString - The compressed
string you wish to uncompress.

Return Value:

Character Data - the extracted
version of cString.

Remarks:

The string to be extracted
must have been compressed with the ZipString() function or other compression function
that is compatible with the compress or compress2 functions in zlib.

cZipFileName - The fully
qualified file name (full path) of the zip file you wish to have extracted.

cOutputFolderName - The
full path to the folder you wish to extract the contents of the zip to.

cPassword - The password
to use when unzipping the file.

lIgnorePaths - If you
wish to ignore the relative paths that are contained in the zip file you would pass
.T. for this parameter. The default value for this parameter is .F. which means that
the relative paths will be respected.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

The file and folders in
the zip file will be extracted into the same folder as the cZipFileName resides in.

Function UnzipOpen()

Signature: UnzipOpen(cZipFileName)

Parameters:

cZipFileName - The file
name or full path of the zip file you wish to uncompress.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipOpen() is used in
conjunction with the matching UnzipClose(). The usual series of function calls would
consist of opening the zip file using UnzipOpen, uncompressing files and/or folders
using Unzip/UnzipTo/UnzipByIndex/UnzipFile, and then closing the zip file using UnzipClose.

Function UnzipClose()

Signature: UnzipClose()

Parameters: None

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipClose() must be called
after issuing an UnzipOpen(). The usual series of function calls would consist of
opening the zip file using UnzipOpen, uncompressing files and/or folders using Unzip/UnzipTo/UnzipByIndex/UnzipFile,
and then closing the zip file using UnzipClose.

Function Unzip()

Signature: Unzip([lIgnorePaths[,
cPassword]])

Parameters:

lIgnorePaths - If you
wish to ignore the relative paths that are contained in the zip file you would pass
.T. for this parameter. The default value for this parameter is .F. which means that
the relative paths will be respected.

cPassword - The password
to use when unzipping the file.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

Unzip() is used between
calls to UnzipOpen() and UnzipClose() to uncompress the entire zip file. The files
and folders contained in the zip file will be extracted to the same folder as the
zip file resides in unless a call to UnzipSetFolder() has been previously issued.
The usual series of function calls would consist of opening the zip file using UnzipOpen,
uncompressing files and/or folders using Unzip/UnzipTo/UnzipByIndex/UnzipFile, and
then closing the zip file using UnzipClose.

Function UnzipTo()

Signature: UnzipTo(cOutputFolderName[,
lIgnorePaths[, cPassword]])

Parameters:

cOutputFolderName - The
folder into which the zip file contents should be extracted.

lIgnorePaths - If you
wish to ignore the relative paths that are contained in the zip file you would pass
.T. for this parameter. The default value for this parameter is .F. which means that
the relative paths will be respected.

cPassword - The password
to use when unzipping the file.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipTo() is used between
calls to UnzipOpen() and UnzipClose(). The functionality of this is similar to calling
UnzipSetFolder() and then Unzip(). The usual series of function calls would consist
of opening the zip file using UnzipOpen, uncompressing files and/or folders using
Unzip/UnzipTo/UnzipByIndex/UnzipFile, and then closing the zip file using UnzipClose.

Function UnzipFile()

Signature: UnzipFile(cOutputFolderName[,
lIgnorePaths[, cPassword]])

Parameters:

cOutputFolderName - The
folder into which the current file (cotained in the zip) should be extracted.

lIgnorePaths - If you
wish to ignore the relative path of the current file you would pass .T. for this parameter.
The default value for this parameter is .F. which means that the relative path will
be respected.

cPassword - The password
to use when unzipping the file.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipFile() is used between
calls to UnzipOpen() and UnzipClose() to extract the currently selected file contained
in the zip file. UnzipFile() is used in conjunction with the UnzipGotoTopFile, UnzipGotoNextFile,
UnzipGotoFileByName, and UnzipGotoFileByIndex functions. You can think of the contents
of a zip file as records in a table. In this sense UnzipGotoTopFile, UnzipGotoNextFile,
UnzipGotoFileByName, and UnzipGotoFileByIndex functions are used to move the record
pointer and the UnzipFile function is used to extract the file that the record pointer
is currently on.

The cPassword is usually
the same for all files within a zip, however it does not need to be the same. Just
keep in mind that passwords can be specified for different files within a zip
and when you are unzipping such an archive you will need to change the cPassword accordingly.

cOutputFolderName - The
folder into which the zip file contents should be extracted.

lIgnorePaths - If you
wish to ignore the relative path for this file that is contained in the zip file you
would pass .T. for this parameter. The default value for this parameter is .F. which
means that the relative path will be respected.

cPassword - The password
to use when unzipping the file.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipByIndex() is used
between calls to UnzipOpen() and UnzipClose() to extract a file by the position (index)
it holds in the zip file. UnzipByIndex() is used in conjunction with the UnzipFileCount
function. You can think of the contents of a zip file as records in a table. In this
sense UnzipFileCount would give you the record count for the table and the UnzipByIndex
function be used to extract a particular file by record number.

The cPassword is usually
the same for all files within a zip, however it does not need to be the same. Just
keep in mind that passwords can be specified for different files within a zip
and when you are unzipping such an archive you will need to change the cPassword accordingly.

Function UnzipFileCount()

Signature: UnzipFileCount()

Parameters: None

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipFileCount() is used
between calls to UnzipOpen() and UnzipClose() to retrieve the number of files that
are contained in the zip file. It does not actually extract anything. UnzipFileCount()
is used in conjunction with the UnzipByIndex function. You can think of the contents
of a zip file as records in a table. In this sense UnzipFileCount would give you the
record count for the table and the UnzipByIndex function be used to extract a particular
file by record number.

Function UnzipSetFolder()

Signature: UnzipSetFolder(cOutputFolderName)

Parameters:

cOutputFolderName - The
folder into which the zip file contents should be extracted.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipSetFolder() is used
between calls to UnzipOpen() and UnzipClose() to set the output folder for extracted
zip contents. It does not actually extract anything. The usual series of function
calls would consist of opening the zip file using UnzipOpen, calling UnzipSetFolder
to set the output folder, uncompressing files and/or folders using Unzip/UnzipTo/UnzipByIndex/UnzipFile,
and then closing the zip file using UnzipClose.

Function UnzipGotoTopFile()

Signature: UnzipGotoTopFile([cExtension])

Parameters:

cExtension - The file
extension to use as a filter for file type. All other file types will be ignored and
only the first file of the type specified will be selected in the zip file.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipGotoTopFile() is
used between calls to UnzipOpen() and UnzipClose() to select a particular file in
the contents of the open zip file. UnzipGotoTopFile() is used in conjunction with
the UnzipFile function to extract a particular file from the zip. By using the cExtension
optional parameter you can select the first record of a particular file type. You
can think of the contents of a zip file as records in a table. In this sense UnzipGotoTopFile,
UnzipGotoNextFile, UnzipGotoFileByName, and UnzipGotoFileByIndex functions are used
to move the record pointer and the UnzipFile function is used to extract the file
that the record pointer is currently on.

Function UnzipGotoNextFile()

Signature: UnzipGotoNextFile([cExtension])

Parameters:

cExtension - The file
extension to use as a filter for file type. All other file types will be ignored and
only the next file of the type specified will be selected in the zip file.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipGotoNextFile() is
used between calls to UnzipOpen() and UnzipClose() to select a particular file in
the contents of the open zip file. UnzipGotoNextFile() is used in conjunction with
the UnzipFile function to extract a particular file from the zip. By using the cExtension
optional parameter you can select the next record of a particular file type. You can
think of the contents of a zip file as records in a table. In this sense UnzipGotoTopFile,
UnzipGotoNextFile, UnzipGotoFileByName, and UnzipGotoFileByIndex functions are used
to move the record pointer and the UnzipFile function is used to extract the file
that the record pointer is currently on.

Function UnzipGotoFileByName()

Signature: UnzipGotoFileByName(cFileName[,
lIgnoreFilePath])

Parameters:

cFileName - The file name
of the file you wish to select in the zip file contents. You can specify a relative
path as well to narrow down the search.

lIgnoreFilePath - If you
wish to just find a file of a particular name in the zip file you can pass .T. for
this parameter and the relative path of the file will be ignored. The first matching
file of the name specified in cFileName will be selected in the zip contents.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipGotoFileByName()
is used between calls to UnzipOpen() and UnzipClose() to select a particular file
in the contents of the open zip file by file name and/or relative path. UnzipGotoFileByName()
is used in conjunction with the UnzipFile function to extract a particular file from
the open zip file. You can think of the contents of a zip file as records in a table.
In this sense UnzipGotoTopFile, UnzipGotoNextFile, UnzipGotoFileByName, and UnzipGotoFileByIndex
functions are used to move the record pointer and the UnzipFile function is used to
extract the file that the record pointer is currently on.

Function UnzipGotoFileByIndex()

Signature: UnzipGotoFileByIndex(nIndex)

Parameters:

nIndex - The index number
of the file to be selected.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Remarks:

UnzipGotoFileByIndex()
is used between calls to UnzipOpen() and UnzipClose() to select a particular file
by the position it physically holds in the contents of the open zip file. UnzipGotoFileByIndex()
is used in conjunction with the UnzipFile function to extract a particular file from
the zip file contents. You can think of the contents of a zip file as records in a
table. In this sense UnzipGotoTopFile, UnzipGotoNextFile, UnzipGotoFileByName, and
UnzipGotoFileByIndex functions are used to move the record pointer and the UnzipFile
function is used to extract the file that the record pointer is currently on.

Function UnzipAFileInfoByIndex()

Signature: UnzipAFileInfoByIndex(cArrayName,
nIndex)

Parameters:

cArrayName - Name of the
VFP array to be created with file information in it.

nIndex - The index number
of the file you want to return information about.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Creates an array with
13 rows (elements) in it. The rows contain various pieces of information regarding
the file that is held in the zip file at nIndex. The following table describes the
contents and data type of each row in the array:

Row

Array Content

Data Type

1

File Name

Character

2

Comment

Character

3

Version

Numeric

4

Version Needed

Numeric

5

Flags

Numeric

6

Compression Method

Numeric

7

DOS Date

Datetime

8

CRC

Numeric

9

Compressed Size

Numeric

10

Uncompressed Size

Numeric

11

Internal Attribute

Numeric

12

External Attribute

Numeric

13

Folder

Logical

Remarks:

The array that is created
will have whatever name was specified by cArrayName. Should the array already exist
it will release it and recreate it. UnzipAFileInfoByIndex() is used between calls
to UnzipOpen() and UnzipClose() to show information about a particular file. The file
is referred to by the position it physically holds in the contents of the open zip
file. You can think of the contents of a zip file as records in a table. In this sense
the UnzipAFileInfoByIndex function is used to refer to and retrieve information about
a particular record number in the zip file.

Function UnzipAFileInfo()

Signature: UnzipAFileInfo(cArrayName)

Parameters:

cArrayName - Name of the
VFP array to be created with file information in it.

Return Value:

Logical - returns .T.
if successful or .F. if the operation has failed.

Creates an array with
13 rows (elements) in it. The rows contain various pieces of information regarding
the currently selected file in the zip. The following table describes the contents
and data type of each row in the array:

Row

Array Content

Data Type

1

File Name

Character

2

Comment

Character

3

Version

Numeric

4

Version Needed

Numeric

5

Flags

Numeric

6

Compression Method

Numeric

7

DOS Date

Datetime

8

CRC

Numeric

9

Compressed Size

Numeric

10

Uncompressed Size

Numeric

11

Internal Attribute

Numeric

12

External Attribute

Numeric

13

Folder

Logical

Remarks:

The array that is created
will have whatever name was specified by cArrayName. Should the array already exist
it will release it and recreate it. UnzipAFileInfo() is used between calls to UnzipOpen()
and UnzipClose() to show information about a particular file. Use the UnzipAFileInfo
function in conjunction with UnzipGotoTopFile, UnzipGotoNextFile, UnzipGotoFileByName,
and UnzipGotoFileByIndex functions to return information regarding a particular file
contained in the open zip file. You can think of the contents of a zip file as records
in a table. In this sense the UnzipAFileInfo function is used to refer to and retrieve
information about the record that the record pointer is currently on within the zip
file.

Function ZipCallback()

Signature: ZipCallback(cFunction)

Parameters:

cFunction - A string denoting
a function, procedure, or method that you want fired whenever a zip event occurs,
such as "MyCallback()".

Return Value: None

Events:

When one of the following
zip events occurs the function/procedure/method specified by the cFunction parameter
will be called. cZipObjectName, nZipEvent, and nZipBytes are private variables created
on-the-fly by the FLL. They will contain the values specified in the table below.

Event Description

cZipObjectName (character)

nZipEvent (numeric)

nZipBytes (numeric)

Zip Opened

Name and path of zip file

0

N/A

Zip/Unzip File Start

Name and path of file being zipped/unzipped

1

N/A

Zip Read or Unzip Write

Name and path of file being zipped/unzipped

2

Number of bytes currently
being read or written

Zip/Unzip File End

Name and path of file being zipped/unzipped

3

N/A

Zip/Unzip Folder Opened

Name and path of folder being zipped/unzipped

4

N/A

Zip Closed

Name and path of zip file

5

N/A

Remarks:

Event handling is started
as soon as you call ZipCallback and pass it the name of a function, procedure, or
object method. ZipCallback provides a way for you to hook into the internal events
happening inside of the VFPCompression FLL. nZipBytes reports the number of bytes
currently being read during zip (2048 bytes at a time) or written during unzip (4096
bytes at a time) of an individual file. In order to turn off event handling simply
call the ZipCallback with an empty length string, such as ZipCallback("").

Function ZipComment()

Signature: ZipComment(cZipFileName,
cComment)

Parameters:

cZipFileName - The file
name or full path to the zip file you wish to add, replace, or delete the comment
on.

If a comment does not exist in the zip file, the string designated in cComment will
be added. However, if a comment already exists in the zip file, the existing
comment will be replaced with the cComment specified. This replacement functionality
allows you to also delete an existing comment in a zip file by passing an empty string
to the cComment parameter.

Virtual EarthBilled as "...the integrated mapping, imaging, search, and data visualization
platform", Microsoft's Virtual Earth (VE)
is an awesome mapping service that can be consumed and manipulated using the VE Map
contol and JavaScript. In order to get a feel for what VE is capabable of, head on
over to the VE Interactive SDK and
try some of the examples provided. There is plenty of documentation, blog
entries and articles available online that provide general VE overviews and detail
the specific features the service provides, so I won't spend a lot of time explaining
what VE is and what it does. I want to explain enough so that you can understand the
goals of the VFP Virtual Earth wrapper class library that I'm presenting in this blog
entry. I figure you can do a few online searches if you're interested in a deeper
or broader understanding of VE and its specific features.

Virtual Earth Map ControlIn order to understand VE from a developer standpoint you'll need to be familiar
with the Virtual Earth
Map Control. The VE Map Control and the JavaScript classes/enumerations that make
it up are provided via a web handler (.ashx file). I find it is more understandable
to think of it as a library rather than a control. The VEMap class (which is part
of the VE Map Control) is what I would consider the actual control that is seen within
a web page. Perhaps I'm splitting hairs here, but I found it to be a helpful distinction
in my mind when first starting to work with VE.

Creating Your First VE Web PageIt's pretty simple to add the VE Map Control to a web page and use it to
instantiate an instance of the VEMap class. Here's the basic HTML needed in order
to create such a page...

You
should note that the first script tag specifies the web handler mapcontrol.ashx as
the source. Also, note how an instance of VEMap is created using the body's onload
and is eventually disposed of in the cleanUp() function via the body's onunload. In
any event, it's pretty easy stuff given the results (go ahead and cut and paste that
bit of code into an HTML file and view it in Internet Explorer).

What
About Desktop Applications?Obviously, given the architecture and heavy reliance on JavaScript, this
stuff was made for web applications. But what about desktop applications that require
geocoding of addresses, distance measurements, scheduling based on zones, or routing
with turn-by-turn directions? I mean, Virtual Earth would work great if there was
just some way to run an HTML page on a VFP form... Oh wait, Microsoft has provided
us the WebBrowser control! Just drop that ActiveX control on a form and make
a quick call to something like, Thisform.Olecontrol1.Navigate("file:///C:\VETest.html").
Looks like we're in
business.

The
only thing that's left to do after that is write all of the JavaScript code that
interacts with the VE Map Control. Awesome! Except that... erm... you may not know
very much JavaScript (even though you're a kick-butt VFP developer), and you may need
VFP code to freely interact with the JavaScript objects (or vice versa), and how the
heck do you get JavaScript to read your database where all of your addresses are stored?
And... And...

Virtual
Earth Wrapper for VFPOK, so the previous section outlines the thought process that eventually
lead to the class library that I built to tackle these problems. While working on
a VFP project for a customer that involved VE, I looked at that list of classes and
enumerations for the Virtual
Earth Map Control version 6.2 and basically thought, "Wouldn't it be super
cool if those were all VFP classes??!!".

I needed
a class library (.vcx) wrapper for VE. This would contain all of the same classes
(VFP equivalents) and a few super slick ways to commuicate and convert between VFP
and JavaScript. With those in hand it would simply be a matter of hammering out the 5-10
thousand lines of code and comments to make it happen. I won't go into the particulars
of everything I ended up doing in order to make this work (you can look in the class
library's code yourself if you're interested), but I did end up using the JSON
class I created to handle most of the conversions and transport (data interchange) between
the two languages.

As
you'll see in the download, the class library wrapper (virtualearth.vcx) is pretty
much a carbon copy of the VE classes. So, most of the documentation and
code that's online for VE is of benefit when using it. In addition
to the virtualearth.vcx, I've also provided a sample project and form (see screenshot
below), so you can try this stuff out right away. The sample project is
also intended to provide a little more information regarding how to do some of the
things that a developer might want to do with VE in a desktop application. I usually
find that learning by example is one of the fastest ways to get up to speed on a technology
I'm not very familiar with.

Special
Thank YouBefore I wrap this blog entry up, I want to express my appreciation for Marc
Lyon's efforts. He provided me early feedback and testing
for this class library. His enthusiasm for this project was also one of the driving
forces behind my completing it as fast as I did.

I
also want to thank my client (you know who you are) who has been kind enough to allow
me to share this work with the VFP Community. You're about as generous and kind as
they come.

What's
NextJust have fun with Virtual Earth in VFP and see if virtualearth.vcx is useful
to you and your applications. In a perfect world, you'd report back to me with any
bugs you find, create some examples of your own and blog about them, or
seek to improve the library in some way. If you do improve virtualearth.vcx,
I'd appreciate it if you would contact me and share the improvements you've implemented.
Thank you in advanced to those of you that decide to follow any of the above
suggestions.

The Visual FoxPro Grid ControlI've often been heard to say that Visual FoxPro's grid is one of the greatest
controls ever devised. I still feel that way, but wouldn't it be nice if some of the
functionality that our customers want implemented in the grid came stock? You know, features
like: sorting, filtering, incremental search, saving user preferences, and exporting
to Excel. It'd be even cooler if this type of functionality could be implemented on
any Visual FoxPro grid (regardless of the recordsource) by simply dropping a class
on a form, setting a single property and writing a single line of code. Well, that's
what I have set out to create with the class I present in this blog entry.

First Shown at Southwest Fox 2008At Southwest
Fox 2008, I presented a session entitled "Creating a Professional
VFP Application from Start to Finish" in which I showed an earlier version of GridExtras.
I've improved on it since then, so if you attended Southwest Fox 2008 you may want
to get the control and sample from here rather than the session download. For those
of you that didn't attend Southwest Fox 2008 (you should have - it was a blast!),
you can see the basic functionality provided by GridExtras in the screenshots provided
at the bottom of this blog entry.

How to Use the ClassI've provided a sample.exe application in the download so you can try it
out and see how it's implemented. However, the basic steps are:

Drop an instance of the gridextras onto your form or container (one gridextras for
each grid you want to enhance).

Set the GridExtras' GridExpression property to a string that will evaluate at runtime
to the grid you are enhancing - "Thisform.Grid1" is the default value for this property.

Call the GridExtras' Setup() method whenever your grid is ready to go. I have no way
of knowing if you are setting the recordsource at runtime, or whether you are adding
columns in code, so this method is provided as a way for you to control when GridExtras
will begin interacting with the grid.

That's all there is to getting GridExtras up and running for a grid in your application.
As I say, I've included a sample in the download, so if you have any questions about
how this is done just review the sample or you can post a comment here on my blog
and I'll do my best to answer it.

Other Properties of NoteThere are a few other GridExtras properties you may want to take note of
and use, such as:

CompanyName and ProductName - these properties are used to determine where to save
the user's grid preferences file which is profile isolated (C:\Users\Craig\AppData\Roaming\MyCompany\MyProduct\gridprefs.tmp
on Vista or C:\Documents and Settings\Craig\Application Data\MyCompany\MyProduct\gridprefs.tmp
on Windows XP)

AllowGridExport, AllowGridFilter, AllowGridPreferences, AllowGridSort - these properties
allow you to turn certain features of the class off or on depending on your needs.

TemplateTable - this property determines where GridExtras looks for the gridextras.dbf
which is used to save the grid templates the user creates (user can save their filters
and sorts to be able to easily recreate them at a later time). This property may need
to be set if you plan to keep the gridextras table out on a network path such as with
the shared database folder for your application.

What's NextJust have fun with GridExtras and see if it's useful to you and your applications.
In a perfect world, you'd find some way to improve it (add additional export formats,
provide a column lock feature, etc.). If you do improve GridExtras, I'd
appreciate it if you would contact me and share the improvements you've implemented.
Thank you in advanced to those of you that decide to do so.

Figure 2: GridExtras has additional features such as the ability
to go semi-transparent and the ability to save the user's preferences (column order
and width).

Figure 3: In addition to the features provided by GridExtras through
a grid's column headers, there is also an icon added to the bottom right of the grid
that allows the user to access the "Grid Templates and Export" screen seen in Figure
4.

Figure 4: The grid templates help the user save grid views so they
can recreate them quickly in the future. The export feature of GridExtras can create
XLS, XLSX, XLB, and XLM Excel formats. The grid's header captions are used to name
the Excel columns when data is exported.