
![]()
Ed. Spoke -- By Carl Warner
An Enhanced Collection Class, Part 1 -- J. M. Edgar
Where's the Marketing? -- Michael Asherman
Software Development Agreements -- J. M. Edgar, JD
Resources-- By Ed.
o Visual
FoxPro
o MS
KnowledgeBase
o Various MS
Links
o .NET
o XML
o General
Interest
Tips 'n Tricks -- By the Membership
o GenLoader
- A WSH Loader Wizard -- John Koziol/George Tasker ![]()
o Benchmarking:
Visual FoxPro 6.0 vs. Inprise Delphi 5 -- Ionut D. Baldazar
o Visual Class Navigators Library -- Alexander
Khudyakov ![]()
o How
can I color code records/cells in a grid? -- Chris R. Chamberlain
o Set
Your Exe's Start Path On The Fly -- Ariel Gimenez
o VFPLife.zip
-- A VFP6 Mathematical Game -- John Koziol ![]()
o Copying
Files? -- Animated GIF Humor
o Joke of the
Month
![]()
By Carl Warner
Yes, we are done accepting articles and tips for this issue of the newsletter. Next month's will be coming along before we all realize it. So, please contribute now for the June issue which will begin posting online on May 6th.
Within a week from now, we will have reached the 30,000 member plateau at VFUG. That is positively awesome! When I started being editor of this newsletter in October of 1997, we had just reached the 7,000 member count. That's a lot of Fox developers and afficionados signing up. We should all be very happy that we are definitely not alone in our interest of FoxPro, whatever version.
VFUG Support for Visual FoxPro -- Besides this monthly newsletter that is available online as it is being created, we have a few avenues offered up to every VFUG member, if they would just choose to use them. We have an online threaded message forum that has gotten more use as of late, we have a list server that gets very little use by the membership, and we have a weekly online chat every Thursday that is only attended by a handful of the VFUG membership. These are all ways to get questions answered, to lurk and learn something, or to show everyone how smart you are by answering the questions the rest of us can't explain to another member. Most of you choose not to avail yourselves of these resources and, not only are you missing out, but so is the whole VFUG and Fox community by so many members not sharing information. If you want to get all of your information from the software vendors, good luck in your developer career.
While many members think that sending me a message that says "unsubscribe" will get you removed from the mailing list, it will not. Why not? Because this newsletter is not on a list server, and it's availability is administered by you in your personal account information. Select Members...Update Your Account Information (hiding on the bottom of the page), Mailing Option (toward the bottom of the page). Then, don't forget to click on the button at the very bottom of that page to Save your account updates.
Now, on to the VFUG List Server which you can actually "unsubscribe" from if you just would "subscribe" to it to begin with. This list broadcasts e-mail questions and answers into your mailbox without the use of a browser or having to consciously (or unconsciously) visit the VFUG website. Many of you who have been on other list servers know how easy it is to sit back and receive e-mail all day without having to Browse for the same kind of Q&A somewhere on the 'net. Plus, you generally setup a Rule in your E-mail reader to shift all incoming mail from any List Server to a specific e-mail folder so you can read the messages at your leisure. How do you get subscribed?
From the e-mail address you want to get VFUG Fox-related e-mails:
Setup to send a text e-mail message (no HTML or RTF)
address the e-mail TO: ListServer@VFUG.Org
Subject doesn't matter, but you could type "subscribe" there.
in the BODY of the message: subscribe VFUGList <put your name here>
Example: subscribe VFUGList Dave Fulton
Then, send that message!
[You will get a confirmation whether it succeeded or not.]
Easy, right? If you haven't done it yet, do it today.
As far as the VFUG Forum that is online at the site and accessible via a browser, visit there often to post questions, comments, or try to answers the questions of others. But, I have to make some comments about that area right now. Everybody involved in VFUG, whether it's you the membership or the officers, are not paid in any way. All of this is a volunteer effort and any advice or help you get is purely out of the goodness of someone's heart. So, what do we ask in return? Do not demand help or criticize someone's effort to help you in any way. Be polite and civil. You are the one asking for free help. If you get an answer that isn't exactly what you're looking for, ask again differently but don't be critical of the advice you just got. If you get advice that seems to require that you spend a little money, it probably is because the solution you need will involve far more time and effort than anyone could possibly give you in the scope of a message or two in any online message. We all don't have the time to do each other's programming work. If you don't follow those simple guidelines, you probably will get your feelings hurt. Treat others as you'd like to be treated and don't abuse your privileges for free advice.
Let's move on to other things.
It looks as though VFP7 will ship on or about June 6th, 2001. That is according to what Amazon.com has posted for a ship date at their site. You can pre-order the upgrade or the full package right now. See below under Resources...Visual FoxPro for those URL links.
That leads us to the contents of this newsletter issue. Since VFP has split off from Visual Studio and is its own little entity within Microsoft, most Fox developers are rightfully concerned about what will be done differently now with the product to help it to gain respect and sales in the general developer world. Many of us know that the effort on the part of MS as a whole in bringing attention to VFP has not been where we'd like to see it. What can you do as a developer to help send a message to MS about how you feel? See the article in this newsletter entitled "Where's the Marketing?". Read it and respond as you wish. The article directs you on where to go on the 'net to get your voice heard within the collective Fox developer community.
Jim Edgar has two great and very different articles in this month's issue. He graciously added a second article when he could tell that we were a little short on material. Well, we're not even close to short now. He first submitted an article on legal aspects of software agreements. This is very good reading especially for all independent contractors doing software development. The second article is the first in a two-part technical series on an enhanced collection class. Check it out.
Also, in the Tips 'n Tricks section there are some great tips and downloadable files. The downloadable files, as usual, are also posted in the VFUG File Archive.
Don't forget to check out the Joke of the Month that won't be available in the e-mailed text version.
And, lastly, there are more than enough internet resources for you to look through to find at least one or two that interest you that you weren't aware of.
The Hentzenwerke Publishing book on getting VFP certified shipped within the last week. I just got a copy of the book on Friday and don't have the time to get a decent book review together for this issue. But, I'll post a review on that book for the June issue and it will be available online, before the June e-mailing, as soon as I get it reviewed. From my quick look at the book, it looks good. But, it's way too early to comment in detail.
Keep hanging in there...
VFP Vendor Product or Service |
|||
| F1 Technologies | Visual FoxExpress Framework | ||
| ProMatrix Corp. | Visual ProMatrix Framework | ||
| Recital Corporation | Tools to get FoxPro Apps To The Web | ||
| Les Pinter Database Development | Database Programming & Training Services | ||
| Walker Computing L.L.C. | VFP Software Business For Sale | ||
-- Ed.
Back To Top
At the VFUG Web Site: |
|||
| VFUG Home | Chat | Real-time online chatting at least every week, usually on Thursday at 3pm EST, 2pm CST, etc. Topics are sometimes announced but are open. | |
| File Archive | Fox-related files, utilities, & newsletter downloadables. | ||
| Lectures | Online lectures, presentations, conferences. | Support | Use the VFUG Forum to post questions and supply answers to anything that is FoxPro-related. Categories are clearly shown. |
| Knowledge Base | Knowledge Base from our members. | ||
| Vendor List | List of current and past Vendors of the Month. | FAQ | Frequently Asked Questions (FAQs) for the Fox community. |
| VFP Resource Center | Tutorials are underway for beginners to MS Visual FoxPro. | ||
| Newsletters | This area allows you to view all VFUG newsletters. | Links | This is an area that is constantly under construction as new web links of interest to the Fox community are posted. |
| Case Studies | Case studies from our members. | ||
| Job Bank | Job opportunities and jobs wanted postings. | Members | This area is where you can view the latest additions to the VFUG community as well as update your vital statistics including mailings. |
VFUG Officers:
| # | Name | VFUG Function | E-Mail Address |
| 1. | John Chambers | Public Relations Manager | |
| 2. | Colin Keeler | Web Manager | |
| 3. | Carl Warner | Newsletter Editor, Marketing Manager, Vendor Relations | |
| 4. | Arnon Gal-Oz | Technical Manager | |
| 5. | Tom O'Hare | Operations Manager |
![]()
An Enhanced
Collection Class, Part 1
J. M. Edgar
Part 1 of a two-part series. This part addresses the requirements for and development of a collection class.
In Part 2, the collection class will used to devise simple solutions
to common programming problems.
A Collection is an ordered set of items that can be referred to and manipulated as a unit. The items or members of a collection need only be related by the fact that they are in the collection. Members in a collection do not have to share the same data type. A collection is similar to an array, but unlike an array has built-in methods to manipulate its members.
Visual FoxPro uses collections built into its foundation classes. The _SCREEN has a Forms collection, a Form owns two collections: the Controls collection and the Objects collection. The Pageframe has a Pages collection. We can read the content of these collections, but cannot otherwise manipulate them. Their usefulness is therefore very limited.
This article addresses the requirements for and creation of our own reusable collection class. Next month, our collection class will used to solve some interesting ( at least I think theyre interesting) programming problems.
Collection
Properties and Methods.
A
collection class of some kind is available in almost development languages and has a
well-understood set of properties and methods.
Items:
The collection itself is just a framework that holds content. Content is arranged as discrete items (terminology
varies considerably, so an item in one collection is a member or
element of another even among Microsoft products).
An Item
is nothing more than an addressable location within the collection where content is
stored. Items may be implemented as objects
or properties of the collection. The
advantage of creating an Item as an object is that we can hang many neat properties on the
object making it both powerful and flexible. Most
graphics collections, for example, define object items that contain information defining
how a particular point is to be drawn. The
disadvantage of object items is that it is complicated to create an indexing object. It is simpler to implement the item as an
indexed property. In FoxPro we already have a native indexed property, the
array, so if we create our items as an indexed property, we can used the native array to
greatly simplify our implementation.
Just as in an array, an integer index is used to
point to the place in the collection where a particular content is stored. Items are addressed as: oCollection.Item[ n ], where n is an
integer.
An index
may be zero-based or one-based. In a
zero-based collection, the first item in the collection is addressed as oCollection.Item[
0 ] and the last item as oCollection.Item[ oCollection.Count 1 ]. These indices are popular in development languages
because they greatly simplify array mathematics.
The
FoxPro standard is the one-based index
inherited from dBaseII. The first item in the
collection is addressed as oCollection.Item[ 1 ] and the last item as oCollection.Item[
oCollection.Count ]. A one-based
index makes it easier to use a FoxPro
array to store the items
in an array without a lot of complex index management.
Items in
a collection are addressed with the following syntax:
oCollection.Item[ n ]. In many
collections, the Item portion of the syntax is a default property. A default property is one that is assumed when
omitted, so that the abbreviated syntax oCollection[ n ] is the equivalent of oCollection.Item[
n ]. In FoxPro, however, we will have to use the
full syntax since the language makes no provision for default properties.
In some
collection classes, items in a collection cannot be addressed directly, but must be
addressed through a method. For example, in
Visual Basic, oCollection.Item( n ) is used to return the content of Item[ n ]. In our collection, we will give the user a choice
of using the method oCollection.Value( n ) to return the content of Item[ n ] or to
address the item such as uContent = oCollection.Item[ n ].
An
Item in a collection usually contains just one data position. But, it could contain many positions, each
position holding content. We would then have
to use a second index to address the item such as oCollection.Item[ n1, n2
] where n1 points to the Item and n2 points to a
discrete position within the Item. Single-
and multiple-position items can be mixed in a collection.
Typically, any kind of data can be stored in a collection, and the data types may be mixed. Indeed, an item may even contain a reference to another collection object, making multi-dimensional collections possible.
Count:
The Count (or Length) property specifies the number of items in the
collection. To ensure that our Count is
always accurate, we will create and raise a Count_Access() event each time Count is
read to recalculate the number of items.
Add():
The Add() method inserts an Item into the collection. It typically has the following minimum syntax:
myCollection.Add( uContent [, nIndex ] ),
where uContent
is the data to be stored in the item pointed to by nIndex.
If nIndex is omitted, the item is
appended to the end of the collection. There
are many variations to the Add() method. For
example in Visual Basic the Add() method accepts parameters that indicate whether new item
is to be inserted before or after an existing item in the collection.
Remove(). The
Remove() method deletes an item from the collection.
For example,
myCollecton.Remove( [ nIndex ] )
removes the item at
position nIndex. If nIndex is omitted, the last item in the
collection is removed. In the standard
collection, nIndex would always be a pointer to a location. In our collection class, we are going to permit
the user to also pass content as an argument to Remove() such as:
myCollection.Remove( uContent ).
where uContent
specifies the content to be removed from the collection.
Remove() then locates the content in the collection and removes the item having
that content.
Non-Standard Properties and Methods
The standard set of collection properties are very basic. To make the collection more useful and flexible, a number of enhancements immediately suggest themselves.
It would be handy to be able to determine the item number at which a certain content is located, to be able to return the content at a location specified by an index and to completely remove all items in a collection with a simple process. In our collection, these processes are implemented as follows:
Value( nIndex ) Returns the
content of the item at nIndex. This is
similar to the Visual Basic Item() method.
Index( uContent ) Returns the index of the item containing
uContent.
Clear() Empties the collection, and nulls any object references that were part of the collection.
Our only non-standard property is NewItemIndex. It specifies as an integer the item index of the last item added to the collection. This property is set in the Add() method.
So without further fuss, lets code.
Creating
a Collection Class
We will base our collection on the VFP custom class. The custom class is a non-visible class that has few native properties or methods and therefore has a very small footprint and little object overhead.
Create a new custom class named Collection in the class library of your choice.
Add the Count property to the class and set it to zero. This is the only property that must exist when the class is instantiated. Otherwise, the Count_Access() event we are going to write below will sometimes not work properly (early- and late-binding issues).
We are going to add all other properties through code in the Init() event. There is no reason these internal properties cannot be added to the property sheet. It is generally good practice to try to minimize the number of properties appearing in the property sheet by including only those that may need to be set. If the property is intended solely for internally use or does not need to be set in the property sheet, there is no reason to expose it in the property sheet for someone to fool with.
Modified Methods:
Init(): Add the following to the Init() event code of your collection class. All this does is add two internal properties to the class. Again, you may elect to add these to your class through your project manager.
* Collection.Init()
*
* Add internal properties to the
class
*
WITH this
* The array that holds the
collection members. This array is always
initialized with
* .NULL. that signifies to the
Count_Access() event that the collection has no members.
.AddProperty(
"Item[1]", .NULL. )
* Specifies the index of a new
member added to the collection. The index is
a pointer
* to
.AddProperty(
"NewItemIndex", 0 )
ENDWITH
RETURN .T.
Destroy(): Include the following code in the Destroy() event. Destroy() calls the Clear() method of the collection just in case the members of the collection contain object references. Any object references in the collection should be set to .NULL. or .F. when the collection is destroyed to prevent hanging references from clobbering your form release.
* Collection.Destroy()
*
* If the members are composed of
object references, the Clear() method sets the
* references to
* hanging object references which
this.Clear()
RETURN
New Methods:
Add(): The Add() method handles all additions and overwrites.
The usual collection supports adding and removing items. Added items are placed at the end of the collection. There is usually no way to insert an item in the middle of the collection. Similarly, once content is added to the collection, there is no means of overwriting the content. The usual, if inelegant, solution is to remove the old content and append a new item with the revised content.
To make our collection more useful, we want to be able to both update existing content and insert a new item at any place in the collection.
We also want our design to recognize that some processes are used more frequently than others. These processes in order of frequency of use are:
· Appending a new item to the end of the collection,
· Updating an existing item in the collection,
· Inserting a new item in the middle of the collection.
Our method design should implement the most frequently used process with the simplest syntax, adding to the complexity of syntax as frequency of use decreases. With this precept in mind, the design requires the least complex syntax to append an item at the end of the collection and the most complex syntax to insert an item in the middle of the collection. The Add() syntax is,
where,
nIndex1 Specifies the item to be added, inserted or replaced.
If this parameter is omitted, zero or greater than Count, the new item will be appended at the end of the collection. If its value is greater than Count + 1, the new item will nonetheless be appended at Count + 1. Items must be added to the collection in sequence. Skipping is not allowed. Item 5 cannot be added before Item 4. If there are two items in the collection, an attempt to add content at item 5 will actually cause it to be added as item 3.
If the nIndex1 points to an item that already exists, it is presumed that the content of the item is to be replaced. unless lInsert is true.
To find out where a
content was actually added, test NewItemIndex that holds the index of the last item
added to the collection.
nIndex2
Specifies the position on the item were the content is to be added. If the position does not yet exist, it will be
created. Positions on an item do not have to
be filled in sequence. Content can be added
to position 3 before it is added to position 1 or 2.
If nIndex2 is omitted or zero, the content will be added at the first (default)
position on the item.
lInsert If true, forces the insertion of a new item. If omitted or false, the content of an existing item will be replaced with the new content. If the item specified in nIndex1 does not exist, it will be added irrespective of the setting of lInsert, which is, in effect, ignored.
Here is the Add() code. The comments explain what each section does.
* Collection.Add()
*
* OVERVIEW: Add a new item to a collection object or
replace an existing item with new * *
content.
* PARAMETERS: tuContent Specifies
he content to be associated with the new item.
*
May be any data type, but may not be .NULL.
*
*
tnIndex1 Specifies
position in the collection at which the new item is
*
to be inserted or where existing content is to be replaced.
*
If omitted, zero, or larger than the Count property setting,
*
the item is added at the end of the collection.
*
*
tnIndex2 Specifies the
column in which the value is to be added.
*
Required for multi-positional items. Otherwise
ignored.
*
*
tlInsert Specifies whether
a new item is to be inserted into the
*
collection.
*
* RETURN: (L)
True if the content was successfully added to the collection.
*
LPARAMETERS
tuContent, tnIndex1, tnIndex2, tlInsert
LOCAL
lnCount, lnNewCount, lnPosition, lnItems
WITH this
* Test and normalize parameters
IF PCOUNT() = 0 .OR. ISNULL(
tuContent )
* The content to be
added to the collection was not passed or passed as a
*
.NULL.
* Just exit. Blank or .NULL. content cannot be added to the
collection.
RETURN .F.
ENDIF
* Since reading Count triggers
Count_Access() we do not want to read it over and
* over
lnCount = .Count
lnNewCount = lnCount + 1
IF PCOUNT() = 1 ;
.OR. VARTYPE( tnIndex1 ) # N ;
.OR. tnIndex1 < 1 ;
.OR. tnIndex1 > lnNewCount
* If the item index was not passed,
is the wrong data type, is less than 1
* or greater than Count + 1; execute
the default operation, i.e. the new item is
* to be added at the end of the
collection.
tnIndex1 =
lnNewCount
ENDIF
* Items must be added to the
collection in sequence without skipping over
* an item. The item index parameter
cannot, therefore, be greater than lnNewCount.
* If the item index is greater than
lnNewcount, reduce it to lnNewCount.
* The user may test NewItemIndex
to find out where an item was actually added.
tnIndex1
= MIN( tnIndex1, lnNewCount )
IF PCOUNT() < 3 .OR. VARTYPE( tnIndex2 ) # "N
* Position index not passed for a
multi-position item. If the item to be
* added or updated is a
multi-position item, assume the content is to be
* placed at position 1 of the item. If the item to be updated is a single
* position item, the content can be
added only to the single position and
* tnIndex2 is irrelevant. Set it to 0 (zero) so lnPositions will be
correctly
* calculated below.
IF ALEN(
.Item, 2 ) > 0
tnIndex2 = 1
ELSE
tnIndex2 = 0
ENDIF
ENDIF
* collection. This value is the greater of tnIndex2 or ALEN(
.Item, 2 ). If the
* item to be added or updated
is a single-position item, lnPositions will be zero.
lnPositions
= MAX( tnIndex2, ALEN( .Item, 2 ) )
* Find out if the content is
to be inserted into the collection or replace
* existing content.
IF (
PCOUNT() = 4 .AND. VARTYPE( tlInsert ) = "L" .AND. tlInsert ) ;
.OR. tnIndex1 > ALEN( .Item, 1 )
* If four parameters were passed,
the 4th parameter is logical and true, force
* an insert.
tlInsert = .T.
lnItems = ALEN( .Item, 1 ) + 1
ELSE
tlInsert = .F.
lnItems = ALEN( .Item, 1 )
ENDIF
* If the array does not
already contain at least that number of columns specified
* in lnPositions, dimension the
array to the number of columns specified in
* lnPositions.
IF
lnPositions > 1 .AND. lnPositions > ALEN( .Item, 2 )
DIMENSION .Item( tnIndex1, lnPositions )
ENDIF
IF tlInsert ;
.OR.
( tnIndex1 > ALEN( .Item, 1 ) ) ;
.OR.
( lnItems > ALEN( .Item, 1 ) )
* If item[ 1 ] is .NULL., the
collection is empty, so the new item
* will be Item[ 1 ], but if item[ 1
] is not .NULL., then we must
* add an item.
IF ISNULL(
.Item[1] )
* The collection should have already
been cleared if the Item[ 1 ]
* is .NULL. But, in case it has not been, clear it now.
.Clear()
ELSE
* We must add a blank row to the
Item array before calling AINS().
* AINS() does not
re-dimension the array before inserting a new item.
* If the array is not
re-dimensioned, the last item in the collection
* will be lost when the new item is
inserted.
IF ALEN(
.Item, 2 ) > 0
DIMENSION .Item( tnIndex1, ALEN( .Item, 2 ) )
ELSE
DIMENSION .Item( tnIndex1 )
ENDIF
* Insert an item into the array at
tnIndex1. The new item is
* inserted just before tnIndex1.
AINS(
.Item, tnIndex1 )
ENDIF
* empty tnIndex2 indicates that
tuContent is to be added to the item at the
* position specified in tnIndex2. Note that tnIndex2 = 0 is essentially the same
* as tnIndex2 = since in both cases
the content will be inserted at position 1 on
* the item.
IF !EMPTY(
tnIndex2 )
.Item[ tnIndex1, tnIndex2 ] = tuContent
ELSE
.Item[ tnIndex1 ] = tuContent
ENDIF
* Save tnIndex1 as a property. NewItemIndex is used to remember the location
* of the last item added, inserted
or updated.
.NewItemIndex
= tnIndex1
ENDWITH
RETURN .T.
Clear(): Clear() removes all items from the collection. It takes no arguments. In a cleared collection, the Count property specifies zero even though there is actually one (.NULL.) element in the Item[] array. FoxPro does not allow an empty array An array must contain at least one element. Therefore, the Count_Access() method determines whether any items are in the collection by reference to the content of Collection.Item[ 1 ]. If it is .NULL. and ALEN( Collection.Item, 1 ] ) = 1, the collection is considered empty (Count = 0).
* Collection.Clear()
*
* Clears all of the items in the
collection
WITH this
* As a precaution,
nullify any object references that may be in the collection.
DIMENSION .Item( 1 )
.Item[ 1 ] = .NULL.
ENDWITH
RETURN
Count_Access(): The Count_Access() event occurs when
the setting of the Count property is read. Count
specifies the number of items in the collection. Since
the number of items is not always the number of elements in the Item[] array, some
adjustment has to be made.
A VFP array can never contain zero elements, but a collection may
contain zero items. Consequently, if the
array contains one element, this event determines whether there are one or zero items in
the collection. Count is treated like a
read-only property. Its value cannot be set
except in this method. If it is set
elsewhere, the setting is ignored.
* Collection.Count_Access()
*
* Return the number of members in
the collection.
WITH this
* If nothing has been added to the
collection or all of its members have been
* removed, the value of .Item will
be .NULL.
IF !ISNULL( .Item[ 1 ] )
.Count = ALEN( .Item, 1 )
RETURN .Count
ENDIF
ENDWITH
RETURN 0
Remove(): The Remove() method deletes a specified item from the collection. An item is specified by passing either its index or its content. It syntax is,
Remove( nIndex | uContent ),
where nIndex specifies the item to be removed or uContent
specifies the content of the item to be removed.
If an index is passed, the item located at the position specified
by the index is removed. If content is
passed, the item having that content is located and removed. Content is not position-specific in
two-dimensional arrays. If the content is
found in any position on an item, the item is removed.
If the content is contained in more than one item, the first item having the
content is removed.
If content is a number, it may look like an index. The second parameter of the method sorts this out. If the first parameter is numeric, it is assumed
an index unless the second parameter is passed as true (.T.). In such case, the number is treated as
content and the collection is searched for the number.
* Collection.Remove()
*
* OVERVIEW: Used to remove an item from a collection. The entire item is removed
*
whether
*
* PARAMETERS: tuParm
The content of an value to be located in the collection OR an
*
integer index
*
*
tlSearch Specifies whether
a numeric tuParm represents the content of an
*
member to be
located in the collection or the index of an item to
*
be removed. If omitted or false, tuParm is presumed to be an
*
index.
*
If true, tuParm
is content to be located in the collection.
*
tlSeach has
*
* RETURN: (L)
True if the specified item is removed.
*
LPARAMETERS
tuParm, tlSearch
LOCAL
ll2Dim, lnElement, lnItemIndex, lnElementCount, lnI
lnItemIndex
= 0
WITH this
* Get the number of elements in the
array. If we need to look for tuParm by
* value, we will look at
every element of the Item[] array then return a row
* index using ASUBSCRIPT()
lnElementCount
= ALEN( .Item )
* Find out if this is a
two-dimensional array.
ll2Dim =
ALEN( .Item, 2 ) > 0
DO CASE
CASE PCOUNT() = 0
* Index not passed. Assume the item is to be removed from the
* end of the collection
lnItemIndex
= ALEN( .Item, 1 )
CASE
VARTYPE( tuParm ) = "O"
* If tuParm is an object reference
to be located in the collection,
* ASCAN() cannot be used because it
will not accept an object as a
* search expression.
FOR lnI = 1
TO lnElementCount
IF ISNULL( .Item[ lnI ] ) .OR. .Item[ lnI ] = tuParm
IF ll2Dim
* If this is a
two-dimensional array, get the
* row that lnI
belongs in from ASUBSCRIPT()
lnItemIndex
= ASUBSCRIPT( .Item, lnI, 1 )
ELSE
* If this is a
one-dimensional array, lnI
* already points to the correct position.
lnItemIndex = lnI
ENDIF
EXIT
ENDIF
NEXT
CASE (
VARTYPE( tlSearch ) = "L" .AND. tlSearch ) ;
.OR.
VARTYPE( tuParm ) # "N"
* tuParm is a value to
be looked up. If tuParm is found in the
collection,
* its row location is calculated and
returned.
lnElement =
ASCAN( .Item, tuParm )
lnItemIndex = ;
IIF(
lnElement > 0 .AND. ll2Dim, ;
ASUBSCRIPT(
.Item, lnElement, 1 ), ;
lnElement ;
)
OTHERWISE
* tuParm is a numeric
row index
lnItemIndex
= INT( tuParm )
ENDCASE
IF lnItemIndex > 0
* A FoxPro array cannot contain
fewer than one element. Consequently if
* the last element is removed, the
element count will still be 1, but the
* value of the array will be .F. We cant tell whether this .F. is due to
* removing the last element or if it
is an actual value. Therefore, when
* the last element is removed from
an array, set it to .NULL.
IF ALEN(
.Item, 1 ) = 1
* We dont have to write code
to do this since the Clear() method
* will set the array to .NULL.
.Clear()
ELSE
* Remove item from the
collection.
ADEL(
.Item, lnItemIndex )
* Redimension the
collection to remove the last row that is now empty.
* But, since an array
cannot be dimensioned to zero rows, make certain
* it will remain
dimensioned to at least one row.
IF ll2Dim
DIMENSION .Item( MAX( ALEN( .Item, 1 ) - 1, 1 ), ALEN( .Item, 2 ) )
ELSE
DIMENSION .Item( MAX( ALEN( .Item, 1 ) - 1, 1 ) )
ENDIF
ENDIF
* Adjust
lnElementCount in case lnElementCount - 1 = 0. This
a little
* obtuse, but stems
from the fact that an array cannot have zero elements
* in FoxPro.
* If Item[] had one
row, and one is removed, it would have zero rows,
* which it cannot
have. Thus since its minimum rows is one, if
this row
* is removed, its row
count is still one (albeit an empty row), so
* adjust
lnElementCount to 2 so the test below works where the number
* of rows in the array
used to be 1.
lnElementCount
= MAX( lnElementCount, 2 )
ENDIF
* If the item was removed and the
collection resized, the current length
* of the collection will be less
than the former length
RETURN ALEN(
.Item, 1 ) < lnElementCount
ENDWITH
Index(): Index() returns the item index of the
first item having the content specified in its argument.
Its syntax is,
Index( uContent
[, lCaseSensitive ] ),
where
lCaseSensitive specifies whether a string content is case sensitive. By default, the search is case insensitive. Only if lCaseSensitive is true will case be considered searching for character content. If the specified content is not found in the collection, 0 (zero) is returned.
* Collection.Index()
*
* OVERVIEW: Locates a value in the collection and returns
an integer representing
*
a pointer to the position in the collection where the value was found.
*
* PARAMETERS: tuValue
The content to be found.
*
*
tlCaseSensitive Specifies whether
strings are to be matched by case.
*
Default is false (matches will be case-insensitive).
*
Has no effect on non-string content.
*
* RETURN: (N)
The pointer to the item in which the value was located.
*
If the value is not in the collection, returns 0 (zero).
*
*
LPARAMETERS
tuValue, tlCaseSensitive
LOCAL lnI,
lnElement
WITH this
* If there are no members in the
collection, return 0 since tuValue cannot
* possibly be
IF .Count =
0
RETURN 0
ENDIF
* Initialize lnElement to zero
lnElement
= 0
* Look for tuValue in the
collection. This search pattern works for
one- and multi-
* positional items.
FOR lnI = 1
TO ALEN( .Item )
DO CASE
* Case insensitive search for a
string
CASE
!( tlCaseSensitive ) ;
.AND.
VARTYPE( .Item[ lnI ] ) = "C" ;
.AND.
VARTYPE( tuValue ) = "C" ;
.AND.
UPPER(.Item[ lnI ] ) = UPPER( tuValue )
lnElement = lnI
EXIT
* Search for any other data type or
case sensitive search for
* a string.
CASE
VARTYPE( .Item[ lnI ] ) = VARTYPE( tuValue ) ;
.AND.
.Item[ lnI ] = tuValue
lnElement = lnI
EXIT
ENDCASE
NEXT
* A members
position in a two-dimensional collection is the row it occupies in
* the Item[] array.
IF
lnElement > 0 .AND. ALEN( .Item, 2 ) > 0
RETURN ASUBSCRIPT( .Item,
lnElement, 1 )
ENDIF
RETURN
lnElement
ENDWITH
Value(): Value () returns the contents of an item at the location in the collection specified by indices passed as parameters. The syntax is,
Value[ nIndex1 [, nIndex2
] ),
where nIndex1 points to the item and nIndex2 to the position on the item. If the specified location does not exist, .NULL. is returned.
* Collection.Value()
*
* OVERVIEW: Returns the content of the member at the
position specified in tnIndex.
*
* PARAMETERS: tnIndex1 An
integer representing the row location in the collection
*
of the value to be returned
*
*
tnIndex2 An integer
representing the column location in the
*
collection of the value to be returned. Applicable
only
*
to a two-dimensional collection.
*
* RETURN: (U)
The value of the member at the location specified by tnIndex,
*
if found, otherwise .NULL.
*
LPARAMETERS
tnIndex1, tnIndex2
WITH this
* Test for a valid tnIndex1
IF VARTYPE(
tnIndex1 ) = "N" ;
.AND.
BETWEEN( tnIndex1, 1, ALEN( .Item, 1 ) )
IF EMPTY( tnIndex2 )
* If this is not a multi-positional
collection, return the content of
* the the item tnIndex1.
RETURN
.Item[ tnIndex1 ]
ELSE
* If this is a multi-positional
collection, return the content of the
* position pointed to by tnIndex2
RETURN
.Item[ tnIndex1, 1 ]
ENDIF
ELSE
IF VARTYPE( tnIndex2 ) = "N" ;
.AND.
BETWEEN( tnIndex2, 1, ALEN( .Item, 2 ) )
RETURN .Item[ tnIndex1, tnIndex2 ]
ENDIF
ENDIF
ENDWITH
RETURN
.NULL.
Conclusion
And that is our Collection class. Pretty simple really.
You could add any number of enhancements. A couple that come to mind immediately are a Count
property that also returns the number of positions on an item and a Sort() method to place
objects in a collection in order. Having
an impatient attitude about tools, I only
build as much of the tool as I need right now. If
more is needed later, it can be built then. So
far, I have had no call for a Sort() method or Count property that returns item positions.
I make constant use of collections to gather objects that I need to
process as a group. They permit me to
devise rather simple, and sometimes elegant, solutions to problems that would otherwise be
very difficult to solve even with extensive code.
Part 2, will describe the use of our new collection class to solve a couple of real-world programming problems.
The first is a solution to the hanging form. A hanging form occurs when an object outside of
the form owns an object reference to the form or some object on the form. The form cannot be closed until all of those
outside references are destroyed. We can use
a collection keep track of the external objects that own references to the form or an
object on the form. When the form or object
is destroyed, it tracks down and eliminates all of these external references. No more hanging forms.
The second problem is the OLE (ActiveX) control that absolutely
refuses to pay any attention to your LockScreen setting.
If you read George Taskers article Smoother (Virtual FoxPro User
Group, June 2000 Newsletter), you know that an ActiveX control on a form is not actually on
the form, but is a separate window of its own.
The form is a window, the ActiveX is a window.
So when the forms LockScreen is set to true, the ActiveX control ignores the
setting because it does not consider itself a part of the forms window. In Part 2 we will use a collection to
facilitate preventing OLE controls from repainting while a forms LockScreen property
is true.
Happy coding.
J. M. Edgar has been a
software developer in xBase languages since 1984. He can be contacted on the Universal Thread or at jmedgar@yahoo.com.
Copyright © 2001 Jurix Data Corporation. All rights under copyright reserved.
![]()
By Michael Asherman
With the release of Visual FoxPro 7.0 scheduled for "late this spring", it's reasonable to wonder when the VFP marketing will begin. Isn't it customary to initiate a marketing campaign in advance of a significant new release? Microsoft started marketing .NET almost a year ago, and it's not even scheduled to ship for months. VFP is more than ready to sell, but isn't it overdue for some marketing?
Why the sudden concern about marketing, or lack thereof, when VFP has survived without it for all these years? The main problem is that chronic neglect has taken its toll on the VFP job market, not just in absolute numbers, but relative to just about every other segment of programming. With the recent downturn in the economy, the VFP job market has nothing left to surrender. To make matters worse, Microsoft's announcement of its intention to remove VFP from Visual Studio has reinforced the general public's doubts about the future of VFP. In the absence of any positive spin from Microsoft on this piece of news, the VFP community faces a public relations disaster. If you've enjoyed making a living from your hard-earned FoxPro programming skills, you have plenty of reason to be concerned.
How could we hope to influence Microsoft's plans for marketing VFP, when years of previous efforts to do that have failed? We can influence Microsoft, and it will be easy if we go about it sensibly. The VB community didn't have to squawk for long to get Microsoft to alter its plans for VB.NET. Of course VFP doesn't have nearly the clout of VB, but we've got some things in our favor. We can be persuasive by sending a coherent message with such clarity and force that it simply can't be ignored. The case has already been made: we need very little from Microsoft and they have nothing to lose and much to gain by obliging us. If you haven't yet reviewed the online discussion that's been going on for the past couple of months, you might find it interesting to look over the VFP Marketing references outlined at http://www.ideaxchg.com/ix07/vm/_sys/toccontu.htm.
What remains to be done, and by whom? The simplest thing you could possibly do would be to make a wish, but that doesn't sound too promising. The second easiest conceivable action you could take would be to utter a single syllable: just say Yes. Let me add your name to the list of those who would consider signing an open letter to Microsoft encouraging them to do a better job marketing VFP. Easy enough, but if it sounds a little scary you should take some comfort in the knowledge that more than 600 people (including 7 Microsoft VFP MVPs) have already put their names on this list, which you can see at http://www.ideaxchg.com/ix07/bymda/mdav0016.htm.
The need for better marketing is an issue on which the VFP community is in virtually unanimous agreement, and by conservative estimates we are at least 100,000, maybe more than a million strong. That is a lot of people, considerably more than the total number of Microsoft employees worldwide. Is there really any doubt that Microsoft would be influenced by a very clear and reasonable request, a modest one at that, from so large a group of their own customers?
If you've taken the trouble to read this far, I'd like to thank you for your interest and finish with this small request: please vote. You can email your vote to me, Mike Asherman, at mda@ideaxchg.com, or use your browser to post a message at http://www.ideaxchg.com/ix08/d4post.htm, or enter your name on the Fox Wiki at http://fox.wikis.com/wc.dll?Wiki~WillMicrosoftMarketVFP. When you vote, indicate how you want your name to appear (pseudonyms are OK) and whether you'd like to include an optional email link. You are welcome to add your own comments on either the Wiki or IdeaXchg if you have the time, but in any case, please take only a single minute more to cast your vote, Yes or No.
Michael Asherman
mda@ideaxchg.com
Back To Top
![]()
Software Development Agreements
There is an ancient truism among attorneys that "An oral contract is not worth the paper it wasn't written on." a truism so old that it was first written in Latin.
And it is absolutely true.
Any lawyer will tell you that, at least in theory, oral agreements are enforceable in court, but in practice they seldom can or will be simply because it is almost impossible to prove the terms of an oral contract. You say one thing, your customer says another. Who is the judge to believe?
Like any other oral contract, verbal agreements to develop, modify or maintain software are practically worthless. If you are working under one now, you are relying solely on the good will of your customer to pay you. If he does not, there is probably little you can do except withhold the software not much help when it comes to paying the mortgage.
I am always surprised at the number of highly intelligent people who will commit a year of more of their time and expertise to a complex project on the mere hope they will be paid sometime in the future.
A written software development agreement is an admitted nuisance. We usually cannot write it ourselves, so we have to hire that most dread of all individuals, an attorney. This costs money and time. And, frankly, odds are that our customers will pay us and that all will go well with our current projects, so we will never actually need it.
Until we do.
When the customer claims he can cancel the project without notice, that he does not have to pay you for the four months of work you have already done, and that he can keep the part you have already delivered: You will be very unhappy that you did not invest in an enforceable written contract with more reasonable termination and compensation provisions.
The written agreement is our safety net. We will usually not need a safety net, but when we do we will be thrilled to have one.
A properly written software development agreement defines the outer boundaries of correct behavior of the parties toward each other, helps prevent disputes, and if disagreements develop, provides a means of resolving them without getting the courts involved. If the dispute does end up in court, it defines for the court the legal duties required of us and our customers and clarifies who owes what to whom, making it much easier and quicker for us to get the compensation to which we are justly entitled (with suitable interest, of course).
Now that you are convinced that you need a written agreement, what you need to do is find a lawyer and get him or her to work. So there is no point in reading the rest of this article.
Well .actually, there are a few good reasons why you should.
First, you will want to know what to look for in a software development contract just to ensure that your attorney has actually covered all the necessary elements. Second, you will need to be prepared to discuss the provisions you want in your agreement no two agreements are the same (If they are, they shouldn't be). And third, as an independent businessman or -woman, you just need to know this stuff because this is the legal environment within which you conduct business.
Specifications
Exactly what is the application going to do? This is what the customer most wants to know. Specifications are the "builder's plan" of the project, the blueprint, the outline, the well, you get it.
It is to everyone's advantage to be as specific as possible. The clearer the specifications, the less likelihood of misunderstanding, customer dissatisfaction and eventual litigation.
Not only is writing clear specifications a necessary part of the agreement, it is almost always our first opportunity to start analyzing and blocking out an application, dividing it into modules and roughing out a progress plan. It is surprising how many developers agree to develop an application with only the vaguest idea of what will be required.
Once the clearest possible specifications are drafted, including flow and process charts if warranted, it is time to review them with the customer. Clarify anything that is not understood, fill in any gaps, and make absolutely certain the customer understands what you propose to do before you drop a contract on his desk. That way there are no surprises.
There are probably as many ways of writing specifications as there are developers writing them, but they generally fall into one of three classes:
Statement of Work: This is the most general form of specification. It provides an overview of the work to be accomplished with limited functional grouping and virtually no technical discussion. This is most suitable for very simple projects or initial letters of intent.
Functional Specifications: The most effective in my experience are functional specifications: clear non-technical explanations of what the application will do with no mention of how the application will do it. This format is the easiest for the customer to understand, and does not tie you down to a specific technical means which you might want to change later without having to renegotiate the contract.
Technical Specifications: With technical specifications, you describe not only what you will do, but how you will do it. The technical specification is the most detailed design blueprint. It often follows acceptance by the customer of the functional design specifications and is included in the contract as an addendum to the functional design. When dealing with IS departments, technical specifications are often the only specifications that are acceptable.
Technical specifications take a lot of time to develop, and you should be paid under a design contract. Never write a technical specification without payment on speculation that you will get the development contract. You may not. Even if you have a gurantee that you will get the contract, there is no guarantee that the development will ever take place.
Irrespective of what form of specifications you use, the specifications should clearly set out what platform(s) and operating environment(s) the application will be written for and what access and support the customer will provide. If you do not limit the platform and operating environments, you may be presumed to have agreed to develop for all of them (more about this below). If you do not clearly spell out what support and access the customer will provide, you may be stuck with completing the application on time and on budget even if the customer does not provide them.
Once you and the customer agree to the specifications, they are usually attached to the written contract as addenda and referred to in the contract language such as ".. the terms and specifications set out in Appendix A and made part hereof by reference as if fully restated anew herein" or something like that.
Timetable
A small project may be done in one swoop, but a large project requires phasing. You agree to deliver a certain part of the project in working order at a certain time, and the customer agrees to pay you a specified amount at that time.
There are a number of advantages to phasing:
· You get paid in installments rather than at the end of the project.
· Problems can be identified early, and resolved.
· If the customer signs off on each phase as "satisfactorily completed", it is very hard for him to claim later that the your work was incomplete or not satisfactory.
· The customer's needs are likely to change as the application is developed and the parties are going to uncover previously undiscovered requirements which usually mean that the application will be modified. Developing in phases is a convenient way to build in checkpoints at which the requirements of the application are reexamined, specifications modified, the timetable and costs adjusted. Almost never are projects completed on the specifications they started with.
There are also a number of important rules to phasing:
· It is critical that the developer agrees to a reasonable delivery schedule. Almost every developer I know (including me) tends to underestimate the time required to code, debug and test an application. So be careful not to shortchange yourself.
· The contract should provide that phases can be aggregated so that if you do not require as much time as you estimated in one phase, the saved time can be added to another phase that has taken more time than you expected.
· There should be a margin of error of at least twenty percent for each phase and for the project as a whole -- more if you can get it -- especially if you are being paid on a time-and-materials basis.
· Timetables should always be identified as "reasonable estimates" rather than "binding commitments" by the developer.
Once the timetable is agreed to, it also should be incorporated in the contract either as an appendix or, since it is usually short, in the text of the agreement itself.
Payment
Development contracts typically specify one of two forms of payment.
The fixed price contract pays the developer a specified amount for the project (and each phase) no matter how much time it takes. This type of contract is usually viewed as most favorable to the customer since he is guaranteed a product at a certain price.
The other form is the cost-plus or time-and-materials contract in which the developer is paid for the time spent and actual costs of the project. The time-and-materials contract is usually regarded as most favorable to the developer.
As a practical matter, however, they are about the same. If we have done our specifications and timetable carefully, the project is probably going to take about as long as we initially figured and since even a fixed price contract is based on our best estimate of the hours required, the cost to the customer should be about the same either way.
Given a choice, I almost always go for the fixed-price contract. If you are a fast coder and can beat the "book" time, you will make more money maybe almost as much as your auto mechanic. Of course, you take the risk of an unforeseen problem that will require an inordinate amount of time to solve but often these situations are so gross that the contract can be renegotiated to cover them.
The payment schedule is usually written in the body of the contract.
Just Whose Code Is It Anyway?
Customers are often astounded to find that even though they have paid you a lot of money to develop their application, they do not own the code from which the application was compiled and linked. The source code and the application are two different things the kind of distinction that lawyers love to make even though they baffle the rest of the world. But just as buying a car does not give you any ownership of the factory that made it, buying an application does not give a customer any rights to the code that made the application.
Nevertheless, you do not want to confuse your customers about ownership of the code. Confused customers are unhappy customers who do not pay. Get the issue of ownership in the agreement and settled up front.
How source code ownership works does not seem to be very well understood in the FoxPro developer community. But it is really quite simple.
You do not own just the code. Your ownership would then be limited to those electronic bits in your computer or the scratches on the paper that make up the physical code. You own more than that. The code is an intellectual property subject to copyright. What you own is the code and the copyright to the code. A "copyright" is just what it says: the "right" to say who can use or "copy" the code. (For an good presentation of elementary copyright law in laymans language, see US Copyright Office: Copyright Basics)
The very instant code is created, it is copyrighted. Contrary to what you may have heard elsewhere, we don't have to actually do anything to copyright code the copyright law takes care of it for us. A copyright attaches to original writings immediately upon writing.
Not only is there an instant copyright, there is an instant copyright holder. That's the owner of the copyright, and that's us, the developers. Actually there are many copyrights, not just one, but unless you are going to sell one of them, you need not be concerned with the many ways lawyers can slice and dice a copyright into its various component parts. When the copyright attaches, you instantly own all of the many copyrights.
We may perfect our copyright, but we don't have to. Perfecting a copyright is no more than a legal formality for registering a copyright with the Copyright Office of the Library of Congress in such a manner that all persons are given notice that we own it. But we do not have to perfect a copyright to own a copyright that any federal count will happily enforce.
One of the purposes of a written agreement is to determine who will own the source code that is, who will hold the copyright. There are various degrees of ownership from the developer wholly owns the code, to the customer wholly owns the code, and every possibility in between.
Some customers insist on owning the code, in which case our price for developing the application should be higher. In fact there should be a specific transfer of ownership to the customer in the agreement at a agreed price over and above the price for developing the application.
Other customers don't care to own the code. But even though the law provides that if the agreement says nothing about who owns the code, the developer owns it by default, there should be a provision to the effect that the developer will retain ownership of the code. That way there are no unhappy surprises.
Protecting Your Toolkit
If the customer is to own the copyright on the code, how do you protect your toolkit? You know, the framework, base classes, functions and procedures you developed over many years that you use as the foundation of every application you write.
Some or all of this toolkit will end up incorporated in the customer's application. If he owns the code, does he then own your toolkit?
Yes, he does, unless the agreement specifically says he does not. So be careful. If the customer owns your toolkit, you cannot reuse it in another project without the customer's permission. You probably do not want to sell all those years of development experience for the price of one application.
Make certain the agreement specifically excludes your toolkit and any modifications you might make to your toolkit while writing the application, listing in general the classes, functions and procedures included in the toolkit. Also ensure that as you are developing the application, toolkit items included in the application are identified as such and a copyright notice written in the code in a prominent place for example at the top of each object method and procedure library. Then keep a dated copy of the code just in case the issue arises later.
To permit the customer to use your toolkit in his application, the agreement should specify that the customer receives a non-exclusive license to use the toolkit items. Notice the "non-exclusive". If you give the customer an exclusive license, its the same as having sold the toolkit outright.
Platforms and Operating Systems
You write the application. It works perfectly. The customer is happy and everyone is all smiles. Then the customer changes from a Novell server to an NT server. It does not work anymore. The customer wants you to come fix your "bug" for free, of course.
The contract should clearly inventory the customer's platforms and operating systems and specifically identify the platforms and operating systems on which the application is required to perform. If the customer "upgrades" to Windows 2004, it should be clear that any modification to the application to accommodate the new operating system is additional work, and will required additional payment.
Which brings us to the subject of
Warranties
Our customers would like us to guarantee that our applications will work perfectly forever a little more warranty than we are likely to give. But giving some level of warranty is inevitable.
I have always worked on the basis of reasonableness. If the customer has paid me a lot of money for a complex application, I should expect to warrant it for a year or even longer. If he has negotiated me down to bare bones, I am not going to be quite so generous.
Actually the software agreement should provide a number of warranties. The Performance Warranty is the one we are talking about now. This guarantees that the application will work per the contract specifications and if it does not we will fix it for free or for a very minimal payment. This is the warranty that incorporates the term certain: 90 days, one year, or whatever time is reasonable.
But there is also the Warranty Of Ownership which states that the customer will get good title to the application. If you have given a security interest to your bank in your work-in-progress to secure an operating loan, you may have to get a release from the bank in order to give the customer good title. This is the same warranty you got on your house. The seller warranted in the deed that he was giving you good and unencumbered title.
The Warranty of Non-Infringement says that in developing the application you have not violated anyone else's copyright, trade secret or patent. So if you used the framework you appropriated from your old employer, be careful. If the employer sues your customer, you will be paying the customer's cost of litigation as well as any damages the court may award to your old employer and any damages awarded to the customer for your breach of contract as well as your own legal costs. This could get expensive very quickly.
These warranties have no term. They are forever. Furthermore, even if they are not included in the software development agreement, they may attach to the agreement as a matter of law in your state as implied warranties - in fact, they probably do. So, to make certain the state does not write them for you, ensure your lawyer does.
Other implied warranties should be specifically excluded. These are the Implied Warranty of Fitness For Purpose and Implied Warranty of Merchantability. Your performance warranty largely duplicates these, but since these warranties come with a boatload of historical clutter, they are a mine field for the unwary.
The Warranty of Fitness may be a special problem. It holds that if you knew or could reasonably be expected to know your customer's purpose in contracting for the software, you may be liable if the software does not fulfill his purpose. This liability may attach even if your work fully meets contract specifications and even if the customers purpose was never included in the contract. Ouch!
Some states do not permit these warranties to be excluded from consumer contracts, but most state laws have no problem with excluding them from commercial contracts between businesses. That's us commercial contracts between businesses. But if you do not exclude them, they are ordinarily included as a matter of law. So make sure they are out, out, out.
Dispute Resolution and Enforcement
One of the principal purposes of a written software development agreement is to head off disputes. But if a dispute arises, the well-written agreement generally provides a means of settling the dispute that does not involve the time and expense of going to court. These are called alternative dispute resolution provisions and generally fall into two classes, arbitration and mediation. They both have the twin advantages of being quick and much cheaper than litigation.
Arbitration is the more formal of the two. An arbitrator acts like a judge, taking evidence from both sides (usually in fairly informal process) and giving a decision. Arbitration can be binding or non-binding. If it is binding, the parties agree to abide by an decision of the arbitrator. If it is not binding, then either party may disregard the decision and sue. But there is usually a penalty clause to the effect that if a party sues and loses, he pays the winner's attorney fees and other litigation costs.
Mediation is the process of resolving a dispute through an objective, neutral facilitator who attempts to aid the parties in reaching a settlement. A mediator has no power to make an independent decision on the issues. He merely assists the parties in reaching a fair accommodation. Obviously this works best if the parties are still being reasonable. When they stop being reasonable, someone might as well go ahead and sue.
Whichever you choose, agree on an arbitrator or mediator in advance and identify the person or organization in the contract. Otherwise, the first thing you may disagree about if a dispute arises is who the arbitrator or mediator will be, then you are only one short step from the courtroom.
Avoid the boilerplate arbitration clause where you pick an arbitrator, the other side picks an arbitrator, then the two of them pick a third arbitrator. If you are going to pay for the time of three professional arbitrators, you might as well just go to court.
Most larger communities have formal arbitration/mediation organizations, many sanctioned by the American Arbitration Association. There are also local and regional associations which can provide referrals to arbitrators and mediators who are often retired judges of some distinction. Actually, it is often possible to get a better, more experienced judge by going to arbitration than by going to court.
The Boilerplate
Everyone makes fun of boilerplate clauses in contracts. They run on and on and are utterly incomprehensive to anyone except lawyers and judges. But boilerplate is both useful and important. The clauses are the distillation of the experience of thousands of lawyers over hundred of years who have found that these specific provisions in a contract tend to produce an expected outcome if the contract is litigated. As for being incomprehensible: if it gets to the point where these clauses are important, you are in a lawsuit and the only person who really needs to understand them is the judge and he or she will.
Some boilerplate clauses are important and should be in every software development contract.
The Whole (Entire) Agreement Clause: This clause says essentially that the written contract (with attachments, modifications, amendments, etc.) is the "whole agreement" and understanding between the parties, and that there are no other documents or oral understandings that are part of the agreement. This prevents the situation where the customer admits that the contract states that he must pay you, but then says that you later told him over drinks that he really did not have to. What you may or may not have told him is not relevant because it is not part of the written contract. The written contract alone states the "whole and entire agreement" of the parties.
The Severability Clause: Your clever lawyer inserted a clause to the effect that in the event of a breach of contract by the customer you get to sacrifice his first born to the Great and Powerful Oz. Any court is going to find that clause void as against the rather sound public policy prohibiting human sacrifice. But you don't want the rest of the contract to be thrown out with it. Hence the severability clause which says that if one part of the contract is found to be void or unenforceable, it is severed, and the rest of the contract remains valid and enforceable according to its terms.
Choice of Forum/Choice of Law: You live in Peoria, the customer is an Alaskan business corporation domiciled in Icefloe, Alaska. If you go to court, you certainly do not want to go to court in Icefloe, you want to go to court in Peoria. Choice of Forum/Law clauses state that in the event of a lawsuit, the suit must be brought in Peoria and the contract interpreted under Illinois law, not the Native Custom applicable in Icefloe, whatever that is. If your customer is a foreign business, it is even more important that you choose law in some United States jurisdiction. You do not want to go to trial in Bejing or Katmandu.
The "No Partnership" Clause: Your customer goes bankrupt and a lot of stiffed creditors are looking around for a deep pocket. They spot you, a successful and wealthy software developer. They sue you, claiming that the software development agreement you entered into with customer created a joint venture and that as a joint venturer with customer you are legally responsible for his debts arising out of the venture. Whoa, Nellie! Bet you never saw that one coming.
To eliminate this real possibility, the agreement should contain a clause that unambiguously identifies you as an "independent contractor" and not "a partner, employee, agent or joint venturer" of the customer, and should clearly state that "neither party is responsible for the debts or obligations of the other". This clause is also helpful if the IRS comes poking around and wants to reclassify you as an employee of your customer to void all those nice business deductions you claimed on your Schedule C (even the ones that were actually true).
Terms and Definitions Clause: Sometimes called the "Interpretation" clause, this is the famous "up shall mean down" clause where every word in the English language seems to have been given another, different and obscure meaning.
Actually the clause is both useful and necessary. Lawyers use words that look and sound like English words but are not English words. They are legal terms of art that have a very specific meaning, unlike English words which may have many shades of meaning. Terms of art are just shorthand permitting a lawyer to use one word rather than a whole phrase to impart precision to a document. It is necessary for a contract to distinguish between a word used as an English word and the same word used as a Legal word. This is the clause that does that.
It also is the clause that defines terms such as "we" when used in a contract. If there are two parties to a contract, which one is "we"? Without the definition contained in this clause, it is often impossible to know.
One word of caution, though. Terms and Definitions Clauses are often the most boilerplate of boilerplate. Many attorneys adopted a favorite terms and definitions clause while still in law school and have not actually looked at it since. Therefore you should. If you find the word "Client" defined but it is the word "Customer" that is used throughout the contract, point it out. Poorly drafted terms and definitions clauses can get you in trouble the courts will use them exactly as written, so you'd best see they mean what you intend.
Written Modifications Clause: This is the clause that provides that the contract may only be modified "in a writing signed by both parties". It means what it says. Oral modifications have no effect. Get any changes in writing signed by the customer. The abuses this protects against are self-evident.
Finding and Hiring a Lawyer
So now you know what goes into a software development contract. Do you know enough to write your own? Probably not. So you need a lawyer. How do you find one?
The kind of lawyer you need is an intellectual property lawyer (sometimes called a patent and trademark lawyer). These are the guys and gals that specialize in contracts for non-tangible property, copyright registrations and often patent applications. In larger communities they are in the Yellow Pages. In smaller communities you may have to (1) go to a larger community or (2) search the Web.
The most common resource lawyers use for finding other lawyers is the Martindale-Hubble Directory which can be found on the Web and in most law libraries. The on-line version is most up to date. Be aware, however, that lawyers pay to get into Martindale-Hubble and their qualifications to do the kind of work advertised are sometimes aspirational rather than real, and not always thoroughly checked.
After you have found a lawyer, how do you make certain he or she is qualified? Like any other trade or profession there are good lawyers and poor lawyers, and lawyers that are good at some things and lousy at others. The best way to hire a lawyer is the same way you hire a plumber: interview, ask questions, and check references.
Lawyers, who generally consider themselves a small step above plumbers, are loathe to give references. But if you insist, you will get them. Note carefully: if a lawyer tells you that giving out the names of his clients is a violation of attorney-client privilege, stand up and walk out. He just lied to you, and there is no reason to believe he will not continue to lie to you. But no ethical lawyer will give out a client's name without checking with the client first. So do not expect to get references immediately.
When the contract is done, read it. If you do not understand any part of it, have the attorney explain it until you do. By the way, if the lawyer can explain it in English, he or she can probably write it in English. Insist on it where possible. Unless your customer has his own lawyer, you may end up being the one who explains it to your customer. It's a lot easier to explain English that it is to explain Legalese.
Writing it Yourself
So you are one of those brave and hardy souls who is willing to take big risks sky-diving, para-sailing, formula one racing, cliff diving. Well, you'll probably be as successful at writing a contract as a lawyer would be writing a FoxPro application.
But before you touch that keyboard, try one of the on-line legal document sites. One or two are not bad. The best for this kind of document may be QuickForms which has fairly good standard software development, modification and licensing agreements for about $20.00 each. Its response is quick, about one minute after your credit card is accepted. These boys do not dither around.
If you are bound and determined to write a software development agreement yourself, at least start with the QuickForm document, then modify it to your heart's content. It does not, by that way, meet all the criteria set out above. But it is much, much better than no agreement at all.
And good luck.
J.M. Edgar
mailto:jmedgar@yahoo.com
J. M. Edgar This
paper is not intended as legal advice nor does it constitute the practice of law. The information provided is believed to be
accurate, but the author assumes no liability for its accuracy, currency or timeliness. This paper does not necessarily describe the
applicable law in alien lands such as Canada or Louisiana. |
Copyright
© 1993-2001 Jurix Data Corporation. All rights under copyright are reserved.
![]()
By Ed.
| VFP Life -- a VFP6 Mathematical Game | John Koziol Freeware |
| Order Microsoft Visual FoxPro 7.0 -- Full Version (U.S. only) | Amazon.com |
| Order Microsoft Visual FoxPro 7.0 -- Upgrade Version (U.S. only) | Amazon.com |
| Documentation templates for database projects | dbtemplates.com |
| Whither Our Fox-Part 2 | FoxTalk March 2001 |
| InstallShield Express Visual FoxPro Home | InstallShield.com |
| Visual FoxPro 6.0 Common Questions | MS Shop |
| FoxLock Product Information | eim software |
| ProMatrix Source Control Kit | ProMatrix |
| An Autoincrementing Class | Les Pinter Learning Center |
| Using ComboBoxes and ListBoxes | Les Pinter Learning Center |
| MSDE Setup | Fox Wiki |
| Visual Studio.NET Beta 2 Slated For June | Techweb News |
| Microsoft scales back changes in VB.Net | ZDNet eWEEK |
| Microsoft redrawing Visual Studio.Net | Tech News - CNET.com |
| Why .NET will not become Microsoft's next OS-2 | TechRepublic |
| Microsoft opens tech center to nurture .Net | Computerworld News & Features Story |
| Seagate Software Crystal Reports® for the Visual Studio.NET Platform |
|
| Visual Studio.NET Comes Up Short | angrycoder.com |
| Microsoft Is All Talk About .NET | Forbes.com |
| Tuning up for C# | Tech News - CNET.com |
| Deciphering Microsoft's .Net puzzle | Network World Fusion, 04-16-01 |
| Serving the Web Windows Forms in Visual Basic.NET | MSDN Magazine, April 2001 |
![]()
By the Membership
GenLoader - A WSH Loader Wizard
| Application | GenLoader.SCX |
| Purpose | The purpose of GenLoader is to allow you
to easily create WSH scripts to update an application executable by using one of two means
to check currency. |
| Requires | Microsoft Visual FoxPro 6.0 or better |
| GenObjs class library (included) |
|
| Open bitmap (included) |
|
| Authors | John Koziol, MCSD, MVP |
| (850) 893-2302 |
|
| digitalelite@earthlink.net |
|
| George Tasker, MVP |
|
| GTasker@compuserve.com |
|
| Version | 1.00 (April 21, 2001) |
| Copyrights | GenLoader ã2001, John Koziol |
| WSH Loader FAQ, ã2001, George Tasker |
Benchmarking: Microsoft Visual FoxPro 6.0 vs. Inprise Delphi 5
I know that all of you would be interested in this kind of benchmark. I
was
too until I got the chance to do it. As a student, you get the chance to meet
people and PEOPLE. I started to work in database programming since I was a
child (5th grade) and I was always interested in benchmarking software
like you too (I think).
I made 4 tests:
I used 2 identical computers (P II 233, 128 MB) and a programmer in Delphi
offcourse (I don't know a thing in Delphi).
We used two tables with 1,000,000 records. In the Delphi (ISAM) table we
used a float field NOT autoincremented-- in the FoxPro one I used a 7 numeric
field long.
We then made some code that will increment the two fields and started the test. We
made the adding code in two ways - append and Insert.
In the append method, FoxPro was adding about 2291 records per second and
Delphi about the same (2154).
In the insert method FoxPro was adding about 8851 records per second and
Delphi about (4456).
So as you see FoxPro is better at doing Inserts than Delphi.
Then we tried the same test but we added a MySQL server.
We couldn't measure the time because my neighbors (also students on the
campus) were playing games on the network and the results where random.
Once Fox was faster and after one minute FoxPro worked like a snail. The same
with Delphi.
In the end of the test, we drank some beer and got drunk.
[Ed. Note: Please don't drink and test. Do your testing
first.]
Well, Delphi is superior in multimedia and stuff like that. But, if you need
speed, you will have it only with FoxPro.
Ionut D. Baldazar
Phone: +4093026686
E-mail: ionutb@c3.campus.usv.ro
Back To Top
Visual Class Navigators Library
The library of visual classes Navigators contains three simple classes - CKhFullNavigator,
CKhMidiNavigator and CkhMiniNavigator. These classes are developed for creation of typical
managing and navigating sets of buttons for forms.
Download Navigators.zip
Alexander Khudyakov
mailto:akhudyakov@yahoo.com
How Can I Color Code Records/Cells In A Grid?
If you want to add colour coding to a grid to be able to visually
differentiate between records and/or cells, add 2 new fields (fore_color C(11), back_color
C(11)) to the ControlSource table of Grid1.
Add a new form method called .GridColours() and a new form property called .nRecNo.
In the .Init event of Grid1 put:-
THISFORM.nRecNo = RECN([TABLENAME])
THIS.Refresh()
In the .AfterRowColChange event of Grid1 put:-
THISFORM.nRecNo = RECN([TABLENAME])
THIS.Refresh()
In the .Column(s) of the grid set .Sparse = .F. and .DynamicCurrentControl =
THISFORM.GridColours().
In the .GridColours() method put:-
WITH THISFORM.Grid1
DO CASE
CASE THISFORM.nRecNo = RECN([TABLENAME])
.Column1.Text1.Forecolor = RGB(255,255,255)
.Column1.Text1.Backcolor = RGB(0,0,128)
CASE TABLENAME.fore_color = [R/W]
.Column1.Text1.Forecolor = RGB(255,0,0)
.Column1.Text1.Backcolor = RGB(255,255,255)
CASE TABLENAME.fore_color = [B/W]
.Column1.Text1.Forecolor = RGB(0,255,0)
.Column1.Text1.Backcolor = RGB(255,255,255)
CASE TABLENAME.fore_color = [G/W]
.Column1.Text1.Forecolor = RGB(0,0,255)
.Column1.Text1.Backcolor = RGB(255,255,255)
ENDC
ENDW
You will find the .Forecolor properties of the cells will reflect the value selected by
TABLENAME.fore_color, with the currently selected record highlighted.
If you want the fields TABLENAME.fore_color and TABLENAME.back_color to determine the
colours displayed, then :-
DO CASE
CASE ALLT(TABLENAME.fore_color) = [255,255,0]
lcColor = [.Column1.Text1.Forecolor =
RGB(]+ALLT(TABLENAME.fore_color)+[)]
&lcColor
lcColor = [.Column1.Text1.Backcolor =
RGB(]+ALLT(TABLENAME.back_color)+[)]
&lcColor
ENDC
Chris R. Chamberlain
support@lithoplas.com
Set Your Exe's Start Path On The Fly
Forget about the problems caused by shortcuts badly created. With this
line
of code your exe will always start in the directory from where it was
called. I think that this line must be the headed one of any program.
If Version(2) = 0 THEN
Cd (LEFT(SYS(16),AT('\',SYS(16),OCCURS('\',SYS(16)))-1))
ELSE
*-- may be here you want to set your development environment...
ENDIF
Ponga su directorio de inicio facilmente
Olvidese de los problemas causados por accesos directos mal creados.
Con esta linea de codigo tu exe siempre se parará en el directorio desde
donde se llamó.
Yo pienso que esta linea debe ser el encabezado de cualquier programa.
If Version(2) = 0 THEN
Cd (LEFT(SYS(16),AT('\',SYS(16),OCCURS('\',SYS(16)))-1))
ELSE
*-- Aqui tal vez quiera setear su entorno de desarrollo...
ENDIF
Ariel Gimenez
arielgimenez@yahoo.com
Argentina
Back To Top
VFPLife.zip - A VFP6 Mathematical Game
| Application | Life.SCX (the Life Form) |
| Introduction | Life was invented by
the mathematician John Conway in 1970. Conway wanted to create a simple set of rules that
defined the evolution of patterns. He was successful; Life starts with very simple
patterns and only a handful of rules and can generate complex, beautiful, and sometimes
unpredictable patterns. The rules are easy: A cell that is turned on is alive; others are not.
Each cycle, all surrounding cells are evaluated. If a cell had one or no neighbors, it
dies. If it has three living neighbors, it stays alive. If it has 4 or more living
neighbors, it dies. An empty cell becomes alive (is born) if 3 neighbors are living. These rules are evaluated for the
matrix as a whole and then plotted. And
reevaluated, replotted
and so on. To quote astrophysicist Mario Livio
from his book, The Accelerating Universe, The moral from this game is simple:
even with a very simple set of fully deterministic laws, an evolving system can achieve a
high level of unpredictablility and complexity. Numerous computer programs have been
written to play Life. Life was one of the very first programs I ever wrote on an Apple ][
in the late 1970s. |
| Requires | Microsoft Visual FoxPro 6.0 or better |
| Lifeforms class library (included) |
|
| Author | John Koziol, MCSD, MVP |
| (850) 893-2302 |
|
| digitalelite@earthlink.net |
|
| Version | 1.00 (April 14, 2001) |
| Copyrights | ã2001, digital elite services and John Koziol |
John Koziol, MCSD, MVP
digitalelite@earthlink.net
Back To Top

Source: http://www.kicken.com/images/copy.gif
Comments area |
I've been a member of VFUG now since October of
1997. I really like this place with all of the many different countries of the world
represented here. It never ceases to amaze me how many different ideas pass through this
website and people are always willing to share what they know with others. When I was
asked about Microsoft's marketing of Visual FoxPro over the last few years, I quickly
replied, "I can't believe it's not better!" Long live Visual
FoxPro!Fabio |
![]()
Some final comments....
Generally, regular CHAT sessions are held every Thursday. For more information on scheduled chat sessions here at VFUG, contact John@VFUG.Org or check the VFUG newsgroup out for current info.
Want to be (in)famous? All you have to do is send VFUG an article or otherwise useful piece of content. We'd be happy to publish most Fox related tidbits, and you can help your fellow developers worldwide!
VFUG is also always looking for additions to our Vendor program. Would your company be interested? Contact Carl@VFUG.Org to get with it. Also, if you are interested in your company placing a banner advertisement on our home page, please contact Carl.
![]()