<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://weblogs.foxite.com/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Andy Kramek</title><link>http://weblogs.foxite.com/andykramek/default.aspx</link><description>Andy's VFP Weblog</description><dc:language>en-US</dc:language><generator>CommunityServer 2.0 (Build: 60217.2664)</generator><item><title>What is a Key?</title><link>http://weblogs.foxite.com/andykramek/archive/2008/09/03/6656.aspx</link><pubDate>Wed, 03 Sep 2008 11:36:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:6656</guid><dc:creator>andykr</dc:creator><slash:comments>6</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/6656.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=6656</wfw:commentRss><description>&lt;P class=subhead1&gt;&lt;FONT face=Tahoma size=2&gt;The word "key" has a very specific meaning in relational database design. It refers to a field whose value is shared between tables. The purpose of such data duplication is to enable a record in one table to be associated with a record in another, thereby allowing the extraction of information that is distributed between the tables. Such an association is referred to as a 'relationship' and is what differentiates relational databases from flat-file databases.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;There are two types of relational key:&lt;BR&gt;&lt;/FONT&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;·&lt;/FONT&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;PRIMARY A primary key is the column, or combination of columns, whose value uniquely identifies an individual record within a table. Neither the primary key, nor any part of it, may have a value of NULL&lt;BR&gt;&lt;/FONT&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;·&lt;/FONT&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;FOREIGN A foreign key is the column, or combination of columns in one table whose value identifies an individual (i.e. related) record in another table. A foreign key may, under some situations, have a value of NULL indicating that there is no related record&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;When defining a relationship between two tables, the table containing the primary key is the "Parent" (also known as the "Referenced Table") and the table containing the foreign key is the "Child" (also known as the "Referencing Table"). Thus the foreign key references the primary and thereby defines how the records in the tables are linked.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;It is a fundamental rule that all tables must have a primary key. This is obvious if you consider a table that contains the real names of my own immediate family as listed below. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;First Name&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Last Name&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Stanley&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Elaine&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Andrew&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Richard&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Angela&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;This is fine and shows my father (Stanley) and mother (Elaine) together with my younger brother (Richard) and sister (Angela). This table's primary key is the combination of first and last name - and that is perfectly allowable. However it is not a good design because when my brother married a girl named "Angela" (and yes, he really did) we encounter a problem when we try to add my new sister-in-law to this table:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;First Name&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Last Name&lt;BR&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Stanley&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Elaine&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Andrew&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Richard&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Angela&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Angela&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Kramek&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Now we have no way to differentiate between my sister, and my sister-in-law! This table has no longer has a primary key and in the absence of any additional information is effectively useless. Of course, the obvious solution is to add some additional, differentiating, information perhaps 'middle initial' – although that is not a particularly good choice since not everyone has one, and the value might still not be unique, and as it happens it isn't (my brother's middle name is "Maurice", my sister's is "Mary" and my sister-in-law happens to be "Margaret"). &lt;/FONT&gt;&lt;/P&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;First Name&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Last Name&amp;nbsp;&amp;nbsp;&amp;nbsp;Middle Initial&lt;BR&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Stanley&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Elaine&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Kramek&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Andrew&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;E&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Richard&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;M&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Angela&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;M&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Angela&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;M&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;&lt;/STRONG&gt;&lt;/FONT&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The point of this example is that it is critically important to define a primary key for each table so that every record can be uniquely and unambiguously identified. So far we have defined the primary key as a combination of columns that are used to store the data. This is referred to as a "compound" key. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The first problem with compound keys (as this example shows) is that it is perfectly possible to end up with duplicate values in several columns and so such keys tend to get ever-more complex as data is added to the database. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;That raises a second problem for compound keys because any table that references a table that has a compound primary key must carry all of the elements of the key. So in order to link any other information to my 'family' table I would need to duplicate all the information in that table - which makes the task of updating information horrendously difficult. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;A much better option would be to use some unique value associated with each individual that can be stored in a single column and, fortunately we have just such a thing available to us – our Social Security Number. SSNs can be stored in a single column and have a value that is unique to the person. This will, finally, allow us to identify each record in the table unambiguously. It is, therefore, a perfectly natural choice for the primary key in any table holding information about people. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;In fact Social Security Number is an example of what is called a "Natural" Key (also known as either an "Intelligent" or "Business" Key) and, in this example it will work very nicely giving us a final, usable, version of the table as shown.&lt;/FONT&gt;&lt;/P&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;First Name&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Last Name&amp;nbsp;&amp;nbsp;&amp;nbsp;Middle Initial&amp;nbsp;&amp;nbsp;&amp;nbsp;SSN&lt;BR&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Stanley&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 814-56-8975&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Elaine&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;823-22-4578&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Andrew&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;E&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 866-41-8537&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Richard&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;M&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 866-77-5411&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Angela&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;M&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 866-23-8544&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Angela&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Kramek&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;M&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 822-39-6425&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;&lt;/STRONG&gt;&lt;/FONT&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;But hang on! Are Social Security Numbers truly unique? The answer is 'no'. In fact there are rules that define what constitutes a valid SSN and consequently there is a finite limit on the actual number of truly unique SSNs that can exist – additionally there are certainly cases on record of duplicate SSNs being issued. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;This highlights the first of several problems associated with the use of natural keys - are they truly unique? In fact, in order to be usable as a primary key, a candidate must possess three attributes:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bulletfirst&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;·&lt;/FONT&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Uniqueness:&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;The value must be unique within the table&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bullet&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;·&lt;/FONT&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Consistency:&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;The value must be capable of being validated in terms of the data type to which it is being assigned. In other words, the specified value must be consistent with the rules for the database.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;For example, we can be sure that 35/45/2009 is not a valid date, but it is impossible to determine whether "123456789" is an SSN that is missing the dashes that format it, or some other value altogether&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bulletlast&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;·&lt;/FONT&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Verifiabity:&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;The value of a natural key refers to some real entity and so we have to be sure that the entity actually exists. For example, we know that an SSN is invalid if the first three digits are higher than '770', but we cannot determine whether "123-45-6789" is a 'real' SSN or not&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;However these are not the biggest issues associated with the use of natural keys as primary keys. The real problem with them is that they are governed by rules that have nothing to do with their use as a primary key, and so are subject to change. Remember, the definition of a primary key is that it must be unique within a table and it may not be null. However, the rules governing SSNs, Vehicle Identification Numbers, Account Numbers, Invoice or Order Numbers, or any of the dozens of other obvious 'natural' keys have nothing whatever to do with 'being unique within a table' which is the fundamental requirement for a primary key. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Does that matter? Emphatically yes! Since the values are governed by rules that relate to their principal function they are subject to restrictions that are irrelevant in the context of a primary key. &lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;For example, a very common requirement for Invoice Numbers is that they should be issued in an unbroken sequence. If an invoice number is also used as a primary key, the system has to ensure that users do not allocate an invoice number that conflicts with that rule, and prevent users from assigning a number and then abandoning the invoice – even though it makes absolutely no difference whether the primary keys form an unbroken sequence or not (so long as they remain unique within the table). Managing the unbroken sequence requirement for a primary key introduces unnecessary complexity (and hence potential for error) into the system.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Change is, of course, the other issue. Since Natural Keys reflect real entities they are liable to change. Primary keys are also stored (as a foreign key) in all related tables, so it is imperative that any changes to the actual value of a primary key must be reflected in all related records. This can have major impacts on a database since it will require that all indexes involving the key be re-built, and may even force physical re-ordering of data within the database. Now, you may be wondering, at this point, why a natural key used as a primary key would ever change. The commonest reason is, of course, 'human error'. Which leads us to the final issue with Natural keys - that of how they are captured in the system.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Natural keys cannot, by definition, be generated by the database. They are also, almost invariably, examples of data that cannot even be derived and so must be input directly by some user (SSN is a good example of this principle; given that the last SSN entered into the system was "876-11-1655" what is the next one going to be? There is, of course, no way to know!). &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The issue is that there is a limit to the amount of validation and verification that can be performed and so it is perfectly possible for errors to be entered into the system. It is axiomatic that in the context of data there is no software solution to "Valid, but Wrong". In other words no matter how much validation and checking you do, you cannot detect that the SSN "123-45-6789" should really have been entered as "123-45-6798"! &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Of course, it is likely that these errors will, eventually be caught. Then they have to be corrected with all the problems that this raises. In fact, in applications based on natural keys, the routines for handling changes and updates to key values are usually the most complex and difficult code in the entire system. So what is the alternative? Enter the "Surrogate Key"&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;A surrogate key is simply a column added to the table whose only function is to be the primary key for that table. Typically these columns are simple integers and all major databases provide for the auto-generation of the values. In SQL Server these columns are called "Identity" columns, in VFP "Auto-Incrementing Fields" and other databases use other names but the principles are the same. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;All of the issues outlined above in respect of Compound keys, and Natural keys, disappear when a surrogate primary key is introduced. As records are added to a table, the database itself generates the next value for that table and assigns it to the new record. There are no issues because the actual value has no significance whatever – it merely has to be unique within the table and the database itself ensures that this is done properly.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;This also means that surrogate keys never need to change. Since the actual value does not matter there is never any reason to change it. Furthermore, because the value is generated as an integer, it requires very little space (4 bytes) and is easily indexed and accessed, making the establishment and maintenance of relationships much more efficient.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;In the context of normalization surrogate keys have the added benefit of not requiring analysis any deeper than the Boyce-Codd Normal extension of third normal form. (This is because all further normalization rules apply only to cases where the primary key consists of more than a single column).&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;In short, there are really no reasons NOT to use a surrogate primary key on all tables in a relational database. In those special cases where data must be generated at different locations and later merged, or when keys must be generated by the client rather than by the server, GUIDs provide a better choice than simple integers as primary keys. But changing the data type does not affect the principle that surrogate keys are the best way to define primary keys.&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=6656" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1060.aspx">Data Management</category></item><item><title>Access and Assign methods can be useful!</title><link>http://weblogs.foxite.com/andykramek/archive/2008/06/21/6300.aspx</link><pubDate>Sat, 21 Jun 2008 14:15:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:6300</guid><dc:creator>andykr</dc:creator><slash:comments>2</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/6300.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=6300</wfw:commentRss><description>&lt;P class=subhead1&gt;&lt;FONT face=Tahoma size=2&gt;Access and Assign methods were introduced into VFP back inversion 6.0, but for many developers they are still very much an "unknown feature". My objective today is to show a couple of examples of where Access and Assign methods can be very useful. Let's start by reviewing just what Access and Assign methods are, and how to create them. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Basically these methods are event-driven in the sense that, if one is defined for a property, it is fired whenever the associated event occurs. So for an assign method, the event is (not surprisingly) the assignment of a value to the property. Note that it doesn't matter how that assignment is made (i.e. whether you use the STORE command, or simply an "=") the method is fired. Similarly for an Access method the triggering event is any reference to the property. Again the actual method does not matter; the method fires whether the property's value is being read into a variable, or used in an object reference or even being output using the "?".&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;You can add Access and Assign methods to any custom property that you create, and to most of the VFP native properties through the Edit Property/Method dialog. Exceptions are the Value property of a control and native properties of ActiveX controls (though you can add them to the properties of a VFP OLE Control that hosts an ActiveX control). &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;To create an access or assign method for a property all you need to do is define a method that is named for the property plus the suffix "_Access" or "_Assign". This is handled automatically in the visual designers in both the "New Property" and "Edit Property/Method" dialogs where you simply check the appropriate box to create the method. In code you simply declare the method in the class definition like any other method. Note that an Assign method MUST define an input parameter to receive the incoming value, while an Access method has no parameters at all.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;DEFINE CLASS xObj AS SESSION&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Define a new property&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;iLastID = 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Create an Assign Method for the new property&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;PROTECTED FUNCTION iLastID_Assign ( tuInval )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Only allows values &amp;gt;= 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF VARTYPE( tuInval ) = "N" AND tuInval &amp;gt;= 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.iLastid = tuInval&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDFUNC&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Create an Access Method for the native DataSessionID property&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;PROTECTED FUNCTION DataSessionID_Access&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Returns the DataSession NOT the datasessionid!&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;RETURN This.DataSession&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDFUNC&lt;BR&gt;ENDDEFINE&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;In this simple definition I have defined a custom property that will only ever accept numeric values that are greater than or equal to zero. Trying to set this property to anything else is simply ignored (we could, of course, return an error).&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;In the case of the DataSessionID property, the access method means that trying to read the current value of the property will return, instead, the current datasession number. The DataSessionID property is, therefore, effectively hidden.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead3&gt;&lt;STRONG&gt;&lt;FONT face=Arial size=2&gt;Creating Strongly Typed Properties&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;One really practical use for an assign method is to create strongly typed properties so that errors cannot occur in your code because a property has an inappropriate value. This can effectively reduce the need to check values repeatedly in code by handling the checking at the point at which a value is assigned. If it fails the test the property rejects the value. The basic methodology is illustrated above, but the code can, and usually is, much more rigorous than indicated there. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;One scenario where I use an assign method like this is when storing a record number, or ID value. Obviously I do not want an invalid number in this case so I use an Assign method that forces the value to be an integer in the appropriate range. Here is the code from a property used to store the current user ID value:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;PROTECTED FUNCTION iUserID_Assign( tnValue )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF VARTYPE( tuInval ) &amp;lt;&amp;gt; 'N' OR EMPTY( tnValue )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Not a number, ignore it!&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ASSERT .F. MESSAGE "User ID must be passed as an integer"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF NOT INT( tnValue ) == tnValue &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Not an integer, ignore it&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ASSERT .F. MESSAGE "User ID must be passed as an integer"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF tnValue &amp;lt; 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Not 0 or higher, ignore it&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ASSERT .F. MESSAGE "User ID must be passed as positive integer"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** We may allow this one – check it&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SELECT userid FROM usertable WHERE userid = tnValue TO SCREEN NOCONSOLE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF _TALLY = 1&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** This is a valid ID&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.iUserID = tnValue&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Ignore it &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ASSERT .F. MESSAGE "User ID must be passed as positive integer"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;ENDFUNC&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Notice the use of the "TO SCREEN NOCONSOLE" in the SQL query – this is an old trick that suppresses the output from a query and is useful when we want is to know if a record exists, or how many records meet the criterion – effectively this is executing a SQL Statement of a type that is not normally permitted in VFP:&lt;BR&gt;&lt;/FONT&gt;&lt;SPAN class=codeonelineChar&gt;&lt;SPAN&gt;&lt;FONT face="Courier New"&gt;&lt;STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;_TALLY = SELECT COUNT(*) FROM [source_table] WHERE [condition]&lt;o:p&gt;&lt;/o:p&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=subhead3&gt;&lt;STRONG&gt;&lt;FONT face=Arial size=2&gt;Running Code with SETALL()&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Another use for an assign method is to enable code to be run on multiple objects using SetAll(). This method, which exists on all VFP Container classes, is used to set the same property to some specific value on all objects in scope that have that property. If an object does not have the property in question, the instruction is ignored in the context of the object and does not cause an error.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;There are some scenarios is which it would be very useful to be able to run some specific code on all objects on a form, or in some container, but only if they actually have the specified method. But SetAll() only applies to properties, not methods, and so to do this we would need to loop through the objects collection and use PEMSTATUS() to determine if each object had the relevant method. If so we would call it and then proceed with the next object. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;A much easier option is to simply create a property with an assign method on the object that calls the required method from inside the Assign. After all, there is nothing in the implementation rules that say that an assign method must actually assign a value! So at any time a value is assigned to the property the appropriate method is called. Since we &lt;I&gt;can&lt;/I&gt; use SetAll() to set properties we avoid the necessity to loop through the collection and test objects. The code is terribly simple:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;PROTECTED FUNCTION iRunCode_Assign( tnValue )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;This.UpdateSelf()&lt;BR&gt;ENDFUNC&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So in the form we can simply have:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codeoneline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;ThisForm.SetAll( 'iRunCode', .T. )&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So any object that has the '&lt;I&gt;iRunCode&lt;/I&gt;' property will immediately call its own '&lt;I&gt;UpdateSelf()&lt;/I&gt;'' or whatever other method is required. You can even create this property and associated assign method as a 'template' method. In other words create the property on your root class but leave the assign method empty. In a specific instance you can then call whatever method you want by simply supplying the necessary code in the Assign method. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The only caveat to this is that you cannot use this approach when sequential execution is needed because there is no way to control, when using SetAll() the order in which objects will execute their code. However, you could still set the property explicitly when you need objects to run their code in a specific order although in that case you might as well call the methods directly.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;An additional benefit of using Assign methods to execute code is that the code being called can be hidden from external objects, or even from sub classes, because the property in question is defined at the same level as the method itself and so can execute that code even when the method cannot be called directly.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead3&gt;&lt;STRONG&gt;&lt;FONT face=Arial size=2&gt;Creating Objects inside an Access Method&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;An access method fires whenever its property is accessed so if that property is used to hold an object reference we can use the Access method to determine whether the object exists. If the object is there, we simply return the reference otherwise we can try and create the object on the fly and, if successful, again return the reference. Why bother? &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Well it avoids the necessity of testing an object reference property to ensure that the object is there. Here is the code from the access method in a form class that uses our Data Manager object. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;PROTECTED FUNCTION oDM_Access&lt;BR&gt;IF VARTYPE( This.oDM ) = "O"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN This.oDM&lt;BR&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF PEMSTATUS( _Screen, 'oDM', 5 ) AND VARTYPE( _Screen.oDM ) = 'O'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** There is an object reference out there already, grab it&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.oDm = _Screen.oDM &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Add the property (no error if already there...)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ADDPROPERTY( _Screen, 'oDM', NULL )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** And instantiate the Data Manager&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SET PROCEDURE TO dataclass, dsetbase, datamgr ADDITIVE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;_Screen.oDM = CREATEOBJECT( 'xDatMgr', This.cDSNToUse )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF VARTYPE( _Screen.oDM ) = "O"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.oDM = _Screen.oDM&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.oDM = NULL&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;ENDIF&lt;BR&gt;RETURN This.oDM&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Our general practice when working is to use a property on the VFP _Screen object to hold the object reference to the Data Manager object. So this code first checks to see if the object exists – if it does it simply grabs the reference and populates the property with it. If there is no Data Manager, this code adds the property (even if the property already exists this will not cause an error). It then instantiates the data manager object and initializes it by using its own connection information (the cDSNToUSe property holds this). If the creation succeeds the object reference is returned otherwise a NULL is returned. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;By encapsulating the process of instantiating the object like this, we can include it in the root class of our form and forget about it. Any form based on this class will now either pick up the existing object reference, or create a new one for all subsequent forms to use, without the need of any specific code in the application start-up. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead3&gt;&lt;STRONG&gt;&lt;FONT face=Arial size=2&gt;Returning Other Values when accessing a property&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Another use for Access methods is to return something other than the actual value held in a property.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;One scenario where you may want to do this is when a property is used to store an ID value but what you actually need is the look-up of that value. Obviously this can be done in code, but if you frequently need it then it makes sense to handle it in an access method like this:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;PROTECTED FUNCTION nKeyCode_Access&lt;BR&gt;LOCAL lcRetVal&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF SEEK( This.Value, 'KeyTable', 'KeyCode' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcRetVal = KeyTable.KeyDesc&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcRetVal = ''&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN lcRetVal&lt;BR&gt;ENDFUNC&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;By extension the same methodology could be used to return an object with multiple values – for example an entire record from a table created using SCATTER NAME. Code similar to that above would simply return a data object, or a NULL:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;PROTECTED FUNCTION cDataKey_Access&lt;BR&gt;LOCAL loRetVal&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF SEEK( This.Value, 'KeyTable', 'KeyCode' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lnSel = SELECT()&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SELECT keytable&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SCATTER NAME loRetVal&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SELECT (lnSel)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;loRetVal = NULL&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN loRetVal&lt;BR&gt;ENDFUNC&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;There are many more cases where Access and Assign methods can be used to simplify your code and I would be interested to hear of any other uses that you have found for these methods.&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=6300" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1061.aspx">VFP Tips and Tricks</category></item><item><title>FoxRockX – Time to Put up, or Shut up</title><link>http://weblogs.foxite.com/andykramek/archive/2008/03/18/5731.aspx</link><pubDate>Tue, 18 Mar 2008 09:21:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:5731</guid><dc:creator>andykr</dc:creator><slash:comments>4</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/5731.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=5731</wfw:commentRss><description>&lt;P class=MsoNormal&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;Over the years much has been made about the FoxPro Community and how important it is to the life and survival of the product. However, as I have said many times&amp;nbsp;(most recently in my "So, no more Visual FoxPro. Now what?" blog entry from May of 2007), it has often been noticeable that the "Community" has been long on talk and short on reaching for their wallets.&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Of course, in the past year Microsoft has announced that VFP will not get another version and that future work, if any, on the product will be limited to critical fixes only. Add to that, the fact that participation at conferences has fallen to the point where from having one major international conference and two or three regional conferences per year in the USA there is now just one regional conference dedicated to FoxPro left – SW Fox had over 150 attendees last year (out of how many thousand FoxPro users in the USA alone - where were the rest of them?) See &lt;/FONT&gt;&lt;A href="http://www.swfox.net/"&gt;&lt;FONT face=Tahoma color=#800080 size=2&gt;http://www.swfox.net&lt;/FONT&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; for details of the 2008 conference...&amp;nbsp;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;Subscription to magazines has dropped off to the point that the two major monthly publications dedicated to FoxPro both essentially died on their feet. Participation in the technical on-line forums has dropped significantly over the past 10-12 months.&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;&lt;STRONG&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;STRONG&gt;Into this picture, at this moment in time, steps Rainer Becker.&amp;nbsp;&amp;nbsp;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;STRONG&gt;Who is Rainer Becker?&amp;nbsp;&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;SPAN class=bodytextChar&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Rainer Becker is the leader of the German Foxpro user group DFPUG since 1993, publishes the small magazine Foxx Professional, runs an interactive Visual FoxPro forum at &lt;/FONT&gt;&lt;A href="http://forum.dfpug.de/" target=_blank&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;forum.dfpug.de&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;, a Visual FoxPro document portal at &lt;/FONT&gt;&lt;A href="http://portal.dfpug.de/" target=_blank&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;portal.dfpug.de&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;, a &lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~VisualFoxPro"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Visual FoxPro&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; eNewsletter at &lt;/FONT&gt;&lt;A href="http://newsletter.dfpug.de/" target=_blank&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;newsletter.dfpug.de&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;, an international online shop at &lt;/FONT&gt;&lt;A href="http://shop.dfpug.com/" target=_blank&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;shop.dfpug.com&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; and has organized the German Devcon&amp;nbsp;since 1994. He has been a speaker at various Microsoft events as the &lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~DevDays"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Dev Days&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;, &lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~FoxTeach"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Fox Teach&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; and &lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~CeBIT"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Ce BIT&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; - at &lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~CeBIT"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Ce BIT&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; fair he was at the VFP demo machine 1993-2005. In his spare time he undertakes consulting work for "&lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~WizardsBuilders"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Wizards Builders&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;" and for "&lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~ISYSGmbH"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;ISYS GmbH&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;" as well as publishing the Visual FoxPro framework "&lt;/FONT&gt;&lt;A href="http://fox.wikis.com/wc.dll?Wiki~VisualExtend"&gt;&lt;SPAN&gt;&lt;FONT face=Tahoma size=2&gt;Visual Extend&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;".&lt;BR&gt;A&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;dditionally he has been a Microsoft MVP for his work in promoting and supporting Visual FoxPro in Europe since 1997 and in 2007 was a recipient of the FoxPro Community Lifetime Achievement Award.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;What has he done?&lt;/STRONG&gt;&lt;/FONT&gt;&amp;nbsp;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;First he has undertaken the enormous (and very risky) task of&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;starting a completely new bi-monthly magazine dedicated to Visual FoxPro – which will be available both on-line and in printed version. That is FoxRockX. &lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;Second he has bought up the entire FoxTalk archive and is making it available, on-line to subscribers to the new magazine, at no additional charge!&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;You can find full details of FoxRockX here: &lt;/FONT&gt;&lt;A href="http://www.foxrockx.com/seite.htm"&gt;&lt;FONT face=Tahoma color=#800080 size=2&gt;http://www.foxrockx.com/seite.htm&lt;/FONT&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; &lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;And you can subscribe by going to either:&lt;BR&gt;&lt;/FONT&gt;&lt;A href="http://www.hentzenwerke.com/"&gt;&lt;FONT face=Tahoma color=#800080 size=2&gt;http://www.hentzenwerke.com&lt;/FONT&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; (USA/Asia)&lt;BR&gt;&lt;/FONT&gt;&lt;A href="http://shopdfpug.com/"&gt;&lt;FONT face=Tahoma size=2&gt;http://shopdfpug.com&lt;/FONT&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt; (Europe)&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;Of course the most important question for many people is, how much will it cost? &lt;BR&gt;O&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;n-Line Subscription (including access to the archives) &lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;US$ 99.00 &lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;( 75 EUR )&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;BR&gt;Printed Subscription (includes on-line and archive) &lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;US$ 158.00 &lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;( 109 EUR )&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;BR&gt;(Note if you are an existing FoxTalk subscriber you can upgrade your existing on-line subscription to the printed version by paying only the difference i.e. US$ 59.00 (34 EUR).&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&lt;FONT size=2&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;Marcia and I have already bought our subscription to the printed version, we have an article in the first issue and plan to continue writing for FoxRockX as long as they will have us.&amp;nbsp;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;However, the success of this venture is going to rely totally on the FoxPro community. &lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;If the community puts its money where its mouth is, it will succeed and could help bring a new lease on life for the product that we all use and love. If the community refuses to support the initiative then the effort will die, and I am very much afraid that all hope for FoxPro will die along with it.&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;So it is my opinion that this really is crunch time for the FoxPro community. &lt;STRONG&gt;&lt;EM&gt;Whatever you may think about the situation personally, either put up and support this international community-led effort, or shut up!&lt;o:p&gt;&lt;/o:p&gt;&lt;/EM&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&lt;FONT size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=5731" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1063.aspx">Comment/Opinion</category></item><item><title>More on the Advantage Database Server</title><link>http://weblogs.foxite.com/andykramek/archive/2008/03/02/5683.aspx</link><pubDate>Sun, 02 Mar 2008 18:23:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:5683</guid><dc:creator>andykr</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/5683.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=5683</wfw:commentRss><description>&lt;P&gt;If you are interested in learning more about the advantage database server that I mentioned on this blog a couple of months ago, then you should visit J D Mullin's blog here:&lt;/P&gt;
&lt;P&gt;&lt;A href="http://jdmullin.blogspot.com/2008/02/getting-started-with-visual-foxpro-and.html"&gt;http://jdmullin.blogspot.com/2008/02/getting-started-with-visual-foxpro-and.html&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;He has put together a very nice ScreenCast (about 10 minutes long) that shows how to get started and some of the things you can do with VFP and Advantage Database Server. &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=5683" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1063.aspx">Comment/Opinion</category></item><item><title>Form Event Sequences</title><link>http://weblogs.foxite.com/andykramek/archive/2008/01/26/5586.aspx</link><pubDate>Sat, 26 Jan 2008 11:47:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:5586</guid><dc:creator>andykr</dc:creator><slash:comments>4</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/5586.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=5586</wfw:commentRss><description>&lt;P class=firstparagraph&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;One of the things that occasionally seems to confuse people is the question of the order in which the events happen when a form is instantiated. The basic answer, in terms of the Form's own events, is given by the acronym "LISA G", which stands for:&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;&lt;SPAN&gt;L =&amp;gt; Load&lt;BR&gt;I =&amp;gt; Init ()each control first, then Form last)&lt;BR&gt;S =&amp;gt; Show&lt;BR&gt;A =&amp;gt; Activate&lt;BR&gt;G =&amp;gt; GotFocus (first control in the Tab order)&lt;BR&gt;&lt;/SPAN&gt;Actually you could argue that this should be "LISAR G" – because whenever the &lt;I&gt;Activate()&lt;/I&gt; event of a form is triggered, a &lt;I&gt;Refresh()&lt;/I&gt; is also initiated. Anyway, while these are the native form events, there are other things happening when a form is instantiated, most notably when a DataEnvironment is involved. &lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;For a form&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;which uses a DE, and contains an exit button and pageframe with two pages the full initialization sequence is:&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM.DATAENVIRONMENT.OPENTABLES&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM.DATAENVIRONMENT.BEFOREOPENTABLES&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.LOAD&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.DATAENVIRONMENT.CURSOR1.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.DATAENVIRONMENT.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.CMDEXIT.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.CMDREFRESH.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE1.GRDCUSTOMER.COLUMN2.TXTCOL2.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE1.GRDCUSTOMER.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE1.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE2.TB01.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE2.CB03.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE2.TB02.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE2.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.INIT&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;FORM1.INIT&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;The whole thing starts with the DataEnvironment &lt;I&gt;OpenTables()&lt;/I&gt; which is an event that calls the &lt;I&gt;BeforeOpenTables()&lt;/I&gt;, and then proceeds with the Form’s &lt;I&gt;Load() &lt;/I&gt;event. After that the sequence is pretty much as expected. The objects contained by the form (Command Button and Pageframe) are initialized first, in the order in which they were added to the form, followed by the containers themselves, beginning with the DataEnvironment. This part of the sequence finishes with the Form’s &lt;I&gt;Init(). &lt;o:p&gt;&lt;/o:p&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;This is, of course,&lt;I&gt; &lt;/I&gt;why parameters passed to a form have to be received in the &lt;I&gt;Init()&lt;/I&gt;. It is the first form method that fires after all the controls are available, but before any of them have actually been updated. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;The next part of the process is concerned with updating the controls from their data sources (which is what the &lt;I&gt;Refresh()&lt;/I&gt; method actually does) and then making the form visible, the events fire as follows:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.SHOW&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE1.ACTIVATE&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.ACTIVATE&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.REFRESH&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.CMDEXIT.REFRESH&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.CMDREFRESH.REFRESH&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.REFRESH&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE1.REFRESH&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.PGFMAIN.PAGE1.GRDCUSTOMER.REFRESH&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.CMDREFRESH.WHEN&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;FORM1.GOTFOCUS&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;FORM1.CMDEXIT.WHEN&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Notice, however, that only the &lt;I&gt;active page&lt;/I&gt; (Page1) of the pageframe gets refreshed. This means that the controls on Page 2 have not yet been updated and this explains why you always need to do an explicit &lt;I&gt;Refresh()&lt;/I&gt; when navigating to a new page. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;There is one thing to watch here, though. If you simply add a "&lt;I&gt;ThisForm.Refresh()&lt;/I&gt;" to the activate event of the first page of your pageframe you would actually end up calling it THREE times when initializing a form. Once (implicitly) from the Form.Activate(), a second time explicitly when the first page of the pageframe is refreshed and finally when the page itself is activated. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=noteoneline&gt;&lt;FONT face=Arial size=2&gt;&lt;EM&gt;Note: This underlines just how important it is to avoid the (way-too-common) practice of calling ThisForm.Refresh() from all over the place. A form level refresh should really only be called when you are certain that it is needed. In other words, ThisForm.Refresh() should always be preceded by some sort of test! &lt;/EM&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=noteoneline&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;One way to handle this is to create a wrapper method for the Form Refresh() and include a test for a form level property that is set by events that will require a form level refresh (like moving the record pointer, or changing the form's mode from "Add" to "View" for example). We use a method named "RefreshForm" to handle this and it looks like this:&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;LPARAMETERS tlSetObjMode&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;WITH Thisform&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF .FormRefreshNeeded&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;.LockScreen = .T.&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF .BeforeRefresh()&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF tlSetObjMode&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;.SetObjMode( This )&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;BR&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;.Refresh()&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;.AfterRefresh()&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;BR&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;.LockScreen = .F.&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;&lt;FONT face="Courier New"&gt;ENDWITH&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=noteoneline&gt;&lt;SPAN&gt;&lt;EM&gt;&lt;FONT size=2&gt;&lt;FONT face=Arial&gt;The parameter is used to determine whether to re-evaluate the form's mode and change any object whose state depends on that mode (handled by the SetObjMode() method). Notice that we also have hooks to "Before" and "After" methods that allow us to modify the behavior, and even cancel a refresh (by returning .F. from BeforeRefresh()) if necessary. By always calling this RefreshForm() method instead of directly calling Refresh() we buy ourselves a lot of flexibility.&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Incidentally, if you instantiate a form from a &lt;/FONT&gt;&lt;B&gt;&lt;SPAN&gt;VCX &lt;/SPAN&gt;&lt;/B&gt;&lt;FONT face=Tahoma size=2&gt;instead of an &lt;/FONT&gt;&lt;B&gt;&lt;SPAN&gt;SCX&lt;/SPAN&gt;&lt;/B&gt;&lt;FONT face=Tahoma size=2&gt; the event sequence is identical except, of course, that there are no dataenvironment events. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;So what happens when we release a form? The answer is that it depends on on HOW the form is released. &lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;&lt;SPAN&gt;There are basically &lt;/SPAN&gt;three ways of killing a form. First you can explicitly call its &lt;I&gt;Release()&lt;/I&gt; method (i.e. &lt;SPAN class=bodytextChar&gt;&lt;I&gt;&lt;SPAN&gt;ThisForm.Release()&lt;/SPAN&gt;&lt;/I&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;B&gt;&lt;SPAN&gt; &lt;/SPAN&gt;&lt;/B&gt;&lt;FONT face=Tahoma size=2&gt;). Second, you can click on the ‘close’ button, which actually calls the form’s &lt;I&gt;QueryUnload()&lt;/I&gt; method and third you can release the object reference to the form: ( i.e.&lt;SPAN class=bodytextChar&gt;&lt;I&gt;&lt;SPAN&gt; RELEASE ThisForm&lt;/SPAN&gt;&lt;/I&gt;&lt;/SPAN&gt;) &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;SPAN class=subhead4Char&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;[1] Form Release() called:&lt;/FONT&gt;&lt;/EM&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;R =&amp;gt; Release&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;D =&amp;gt; Destroy (Form FIRST, then all contained controls in reverse of the Init order)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;U =&amp;gt; Unload&lt;BR&gt;&lt;/FONT&gt;&lt;SPAN class=subhead4Char&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;[2] Close button used:&lt;/FONT&gt;&lt;/EM&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Q =&amp;gt; QueryUnload&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;D =&amp;gt; Destroy (Form FIRST, then all contained controls in reverse of the Init order)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;U =&amp;gt; Unload&lt;BR&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN class=subhead4Char&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;[3] Object Reference released (note that neither Release(), nor QueryUnload() are fired in this case):&lt;/FONT&gt;&lt;/EM&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;D =&amp;gt; Destroy (Form FIRST, then all contained controls in reverse of the Init order)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;U =&amp;gt; Unload&lt;BR&gt;In each case, the form's DE does not get released until AFTER the form Unload - so tables are still available in the Unload as well as properties - but no controls. &lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;There is a potential problem here though. Notice that the first event common to all three methods is the &lt;I&gt;Destroy()&lt;/I&gt;. However, once the Form's Destroy fires there is no getting out of it, even a NODEFAULT will not stop the release process. That means that there is no single native method or event in which you can place code that checks to determine whether a form should be released or not. (It also means that if you release the object reference, it absolutely will be killed - there is no way to undo such this action because it fires the &lt;I&gt;Destroy()&lt;/I&gt; directly).&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;If you do need to check that it is Ok to release a form you will either have to disable the 'close' button on all your forms, or place the checking code in a custom method that is called from BOTH the form's &lt;I&gt;Release()&lt;/I&gt; and &lt;I&gt;QueryUnload()&lt;/I&gt; events because a NODEFAULT in either of these methods will halt the destruction of the form. This code, in both events, will do the trick:&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;IF NOT ThisForm.OK2Destroy()&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;NODEFAULT&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;ENDIF&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The most usual reason for this sort of code is to prevent users from closing a form with uncommitted changes pending, so the &lt;I&gt;OK2Destroy()&lt;/I&gt; will typically call the form's &lt;I&gt;CheckForChanges()&lt;/I&gt; method. As usual we prefer to call 'control methods' that then call the actual 'action' methods so that if we do need to modify behavior there is a place to do it from. &lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=5586" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1062.aspx">VFP How-To</category></item><item><title>Advantage Database Server V9.0 released as Beta</title><link>http://weblogs.foxite.com/andykramek/archive/2008/01/05/5530.aspx</link><pubDate>Sat, 05 Jan 2008 10:58:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:5530</guid><dc:creator>andykr</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/5530.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=5530</wfw:commentRss><description>&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Sybase Anywhere have been working on a Visual FoxPro compatible version of the Advantage Database Server for some time and, just before Christmas, the long-awaited Version 9.0 was released in Beta. You can download the Beta version (time limited until the end of March 2008) from here:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;A href="http://devzone.advantagedatabase.com/dz/content.aspx?Key=20"&gt;&lt;FONT face=Tahoma color=#800080 size=2&gt;http://devzone.advantagedatabase.com/dz/content.aspx?Key=20&lt;/FONT&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;For a full version you will need to download the Server, the Data Architect and the Advantage OLEDB and ODBC drivers. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Now some of you may be wondering why this should be news, or maybe you have never heard of the Advantage Database server. There is a lot of very good information on this neat little tool on their web site and I have been very impressed with it but there are a couple of things that should make it of immediate and very real interest to all VFP Users:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;[1] The ODBC driver will access all current VFP Data Table and Data Types. Yes, including Auto-Inc, Varchar() and all the newest features. For any of you who have been unable to move off VFP 6.0 because you need to access your data through ODBC there is now a solution!&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;[2] The advantage server can be used to connect directly to DBF files, replacing the DBC with a true multi-threaded, multi-user, remote server that includes, among many other features, a SQL Debugger (yes, you can step through SQL Queries one line at a time!), replication and merge functionality, and that runs on Windows, Linux or Netware.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;I have been playing with this version for a couple of weeks now and have been very impressed by its capabilities and potential. I am not going to try and cover all the things that the Advantage Database Server can do, but perhaps the one thing that is worth mentioning is that it can remove the necessity for users to have direct access to your DBF files. This requirement has long been one of the major objections to the DBF file as a data store because of the ease with which these files can be read and even corrupted. The Advantage Database Server makes this direct access unnecessary. DBF Files can now be hidden away from prying eyes (only the server needs access) and the server also manages locking and concurrency issues. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Coming so soon on the news that Microsoft are abandoning VFP to its fate, it is nice to see that someone out there thinks that there is still some value in the product and is willing to put some effort into it. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Here is what the Help File has to say about VFP support:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead1&gt;&lt;STRONG&gt;&lt;FONT size=5&gt;&lt;FONT face=Arial&gt;Visual FoxPro 9 File Format Support&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=subhead1&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;Advantage support for FoxPro DBF tables and CDX/IDX index files has been enhanced to include all data types supported by Visual FoxPro 9.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;This includes support for Auto-increment, BLOB (Binary Large Objects), Currency, DateTime (timestamp), VarBinary, and VarChar field types.&lt;SPAN&gt;&amp;nbsp; &lt;BR&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;Null field support has also been added to give true SQL-style NULL handling for Visual FoxPro DBF tables.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Support for true unique (candidate) indexes is now also available on DBF tables; this allows for better SQL optimization, the definition of primary keys, and the creation of referential integrity rules.&lt;SPAN&gt;&amp;nbsp; &lt;BR&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;EM&gt;&lt;FONT size=2&gt;&lt;FONT face=Arial&gt;Additionally, support for long field names with DBF tables has been added.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;In addition to supporting the robust and high-performance Advantage proprietary locking on the new table format, Advantage also supports Visual FoxPro compatibility locking, which allows you to use Advantage-enabled applications to share tables with existing Visual FoxPro applications that are not using Advantage.&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=subhead1&gt;&lt;STRONG&gt;&lt;FONT size=5&gt;&lt;FONT face=Arial&gt;Visual FoxPro Upsizing Utility&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=subhead1&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;To complement the enhanced support for Visual FoxPro tables, this release includes an upsizing utility that aids in updating your application to use your existing Visual FoxPro tables with Advantage Database Server.&lt;SPAN&gt;&amp;nbsp; &lt;BR&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;It is not necessary to update your tables or indexes to allow them to be used with Advantage Database Server.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;However, the upsizing utility will export much of the information in your Visual FoxPro Database Container (.DBC) to an Advantage Data Dictionary (.add) to allow support for features such as long field names, primary keys, referential integrity, and triggers.&lt;SPAN&gt;&amp;nbsp; &lt;BR&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;This utility, written as a Visual FoxPro application that you can modify, currently has the ability to export view definitions, referential integrity rules, default field values, field and table validation rules, and primary keys.&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;If you are still not sure what this is all about, here is an extract from the Overview section of the (very comprehensive) help file for Version 9.0:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;Advantage Database Server is a high performance client/server RDBMS for stand-alone, networked, Internet, and mobile database applications. Advantage Database Server allows developers the flexibility to combine powerful SQL statements and relational data access methods with the performance and control of navigational commands. Advantage has native development interfaces designed to leverage existing knowledge of popular development tools. With optimized data access methodology for easily delivering unparalleled performance, Advantage provides security, stability, and data integrity while being completely maintenance-free.&lt;BR&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;The Advantage Database Server is the key to improved database performance in network environments. The server can be visualized as an intelligent controller that reduces competition for resources and off-loads much of the work normally performed by each client workstation. It is responsible for all database access, including all reading and writing of data, and lock management. Working with the network operating system, the Advantage Database Server processes data requests and returns the information to the network clients.&lt;BR&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;The Advantage Database Server supports the NetWare, Windows, and Linux operating systems. The Advantage Database Server for NetWare is implemented as a NetWare Loadable Module (NLM). An NLM is simply an executable for the NetWare operating system. The Advantage Database Server for Windows operates as a Windows Service.&lt;BR&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;Note&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;The Advantage Database Server for Windows is a Service. It cannot be run as a standard Windows application. See Installing and Starting the Advantage Database Server for Windows , for more information on Windows Services and how to start them.&lt;BR&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;
&lt;P class=MsoNormal&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;The Advantage Database Server retrieves requests for database operations to be performed on behalf of the clients. The Advantage Database Server locates tables on the server and processes the database operations. The result of the operation is then returned to the client across the network, eliminating the need to send the database to the client for processing. This provides far better concurrency control and system integrity than is otherwise available.&lt;BR&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;EM&gt;&lt;FONT face=Arial&gt;Traditional non-client/server applications send raw data from the server across the network to be processed on the workstation. With the Advantage Database Server, much of the data is processed by the Advantage Database Server on the file server. By decreasing network traffic, you increase performance.&lt;BR&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;EM&gt;&lt;FONT size=2&gt;&lt;FONT face=Arial&gt;The Advantage Database Server integrity system ensures that database updates either run to completion or do not begin. The Advantage Database Server will not execute partial commands. This means that the integrity of your database no longer depends on the stability of the workstations on the network. Because the Advantage Database Server is responsible for all database access (on behalf of the clients), it can do a far better job of concurrency control than traditional systems, where concurrency must be synchronized between remote workstations. Better concurrency control means better multi-user performance.&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;What we have still to find out is what the new version will cost! But if you are looking for ways to leverage your VFP data and applications without the costs&amp;nbsp;involved in&amp;nbsp;porting to SQL Server or MySQL, this may be something that you should take a long hard look at - for the next couple of months at least it&amp;nbsp;will be&amp;nbsp;freely available for download and evaluation.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=5530" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1063.aspx">Comment/Opinion</category></item><item><title>Frankfurt Developer Conference is just 1 Week Away</title><link>http://weblogs.foxite.com/andykramek/archive/2007/10/29/5243.aspx</link><pubDate>Mon, 29 Oct 2007 09:34:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:5243</guid><dc:creator>andykr</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/5243.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=5243</wfw:commentRss><description>&lt;P&gt;By now I am sure you will have seen the rave reviews for SW Fox held last week in Mesa, Arizona. Rick, Doug and Tamar put together a fabulous conference which was really positive and upbeat.&amp;nbsp;I have not been to such a great VFP conference in the US in many years!&lt;/P&gt;
&lt;P&gt;However, for those of you who missed it, there is still time to get to the 14th Microsoft Visual FoxPro Developer Conference 2007 to be held&amp;nbsp;&amp;nbsp;between 8/10th November in Frankfurt, Germany.&lt;/P&gt;
&lt;P&gt;Apart from the setting, the food, the free beer and the warmth of the welcome, the Speakers for the English Track&amp;nbsp;list is impressive including:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Marcia Akins&lt;/LI&gt;
&lt;LI&gt;Craig Bernston&lt;/LI&gt;
&lt;LI&gt;Steven Black&lt;/LI&gt;
&lt;LI&gt;Yair Alan Griver&lt;/LI&gt;
&lt;LI&gt;Doug Hennig&lt;/LI&gt;
&lt;LI&gt;Vennelina Jordanova&lt;/LI&gt;
&lt;LI&gt;Andy Kramek&lt;/LI&gt;
&lt;LI&gt;Rick Schummer&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;For just 999 Euros (less than £700!) you get the full conference registration indcluding meals,&amp;nbsp;(and WHAT meals they are). &amp;nbsp;There are special conference rates at the hotel, and the flights are really good value at this time of year too.&lt;/P&gt;
&lt;P&gt;The range of topics available is extensive and there is no doubt that it is great value for money. You can find details here: &lt;A href="http://devcon.dfpug.de/"&gt;http://devcon.dfpug.de/&lt;/A&gt;&amp;nbsp;and an English Registration form is at &lt;A href="http://portal.dfpug.de/dFPUG/Dokumente/Konferenzen/Konferenzprogramm2007/Anmeldeformular%20engl.doc"&gt;http://portal.dfpug.de/dFPUG/Dokumente/Konferenzen/Konferenzprogramm2007/Anmeldeformular%20engl.doc&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;For reviews of previous Frankfurt Devcons check out the Wiki at &lt;A href="http://fox.wikis.com/wc.dll?Wiki~GreatThingsAboutDevconGermany~VFP"&gt;http://fox.wikis.com/wc.dll?Wiki~GreatThingsAboutDevconGermany~VFP&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;Hopefully a few more of my countrymen will make the effort this year and I look forward to seeing you there. (Apart from anything else, it is always embarassing when I am asked why I am the only Brit in the place &lt;img src="/emoticons/emotion-1.gif" alt="Smile [:)]" /&gt;)&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=5243" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1063.aspx">Comment/Opinion</category></item><item><title>Creating Classes with a Factory</title><link>http://weblogs.foxite.com/andykramek/archive/2007/08/04/4508.aspx</link><pubDate>Sat, 04 Aug 2007 12:22:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:4508</guid><dc:creator>andykr</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/4508.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=4508</wfw:commentRss><description>&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;This implementation uses the abstract factory pattern to avoid one of the most difficult code maintenance issues. This is the problem that arises whenever we need to change either the class name, or the source library (or both) from which we want to instantiate an object, or simply need to make changes to an existing class definition. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;To change a name is fraught with peril and the only way to do it is to search the source code for all occurrences of the item that we want to change and replace it with the new one. This works just fine providing that we don’t miss one, or mistype either the name (or library, or both!) The more occurrences we have to fix the more likely it is that a mistake will be made – even using tools like “Code References”. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Even routine maintenance and enhancements can cause problems, especially when a team of developers is involved because a class library cannot be shared. So there is always a danger that changes made by one person get overwritten by changes in a different class made by another. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The implementation described here addresses both of these issues.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;What is a Factory?&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;A factory class is basically one whose function is to instantiate other classes. It is an example of the more general ‘Abstract Factory’ design pattern, which is defined in &lt;I&gt;“Design Patterns – Elements of Reusable Object-Oriented Software” (Gamma, Helm, Johnson &amp;amp; Vlissides, Addison Wesley 1997) &lt;/I&gt;as:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=noteoneline&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;Provide an interface for creating families of related or dependent objects without specifying their concrete classes&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;What are the components of the factory?&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;The factory pattern requires a minimum of two components and, usually three (See Figure 3). &lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bulletfirst&gt;&lt;FONT face=Tahoma size=2&gt;The Client – the object that requires an instance of a class. This is where, in conventional code, the &lt;I&gt;CREATEOBJECT()&lt;/I&gt; (or &lt;I&gt;NEWOBJECT()&lt;/I&gt;) function call would normally be located &lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bulletfirst&gt;&lt;FONT face=Tahoma size=2&gt;The Factory - this is usually instantiated as a stand-alone object at application startup, although it can also be implemented as a method on an Application Object. &lt;/FONT&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bulletfirst&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;The Metadata - this, is what makes this implementation so useful in my opinion, and why VFP is such an ideal tool in which to implement this particular pattern&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=figure&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;Figure 1: Abstract Factory Components&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=figure&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;&lt;SPAN&gt;&lt;IMG src="/photos/andykramek/images/4509/original.aspx" border=0&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=figure&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;When should I use a Class Factory?&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The short answer is always! There is really no reason not to use a factory to instantiate objects and the only real question is whether it should be implemented as a global object in its own right, or as a method on some other (i.e. Application) object. My personal preference is to create the factory as a separate object because I always use metadata to drive it, and it helps keep things tidy if the factory can run in its own private datasession.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The key thing to remember is that the role of the metadata in the abstract factory is to separate the functionality from the information that is used to drive it. In this case we are concerned with instantiating objects which would normally be handled explicitly by using code like this:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;loObject = CREATEOBJECT( ‘MyClass’ )&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;OR&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codelastline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;loObject = NEWOBJECT(&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;‘MyClass’, MyClassLibrary’ )&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;As noted above, problems arise with this if either the class name, or the source library changes, or even if we want to test a new version of a class because we must either change the original class definition, or change the calling code. Either way we have to update, recompile and re-distribute the application. The solution is that, instead of hard coding the instantiation directly into the application, we hand the task off to another object. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;In order to do that we need to associate a non-definitive “key” (that will be used in the code) with the actual class, and library names that can be looked up at run time to instantiate the correct class. Since the interpretation of the key name is handled inside the factory, there is never any need to amend the source code when changes to either class, or class library, are made. All that is needed is to change the metadata.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;Implementation&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The metadata table for the factory class is shown in Table 1. Notice that the metadata, in addition to the Key, Class and Library names includes a memo field which we use to define default values for specific properties by listing them as Attribute/Value pairs. There is also a logical field which allows us to disable particular classes when necessary (more on that later).&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;Table 1: Factory Class Metatdata&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;&lt;IMG src="/photos/andykramek/images/4510/original.aspx" border=0&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The factory class itself is defined programmatically using the session base class so that it always runs in its own private datasession. This obviates any possibility that the tables it uses will be affected by the application itself. The interface is very simple as shown in Table 2:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;Table 2: Factory Class Interface&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;&lt;IMG src="/photos/andykramek/images/4511/original.aspx" border=0&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;In practice we define TWO identical tables. The first is named “&lt;I&gt;classes.dbf&lt;/I&gt;” and is the production version of the table, the second is named “&lt;I&gt;wipclasses.dbf&lt;/I&gt;” (for &lt;I&gt;Work-In-Progress&lt;/I&gt;). The code in the factory always checks for the key in the &lt;I&gt;wipclasses&lt;/I&gt; table first so that we don’t actually have to modify things on the production side (&lt;I&gt;classes.dbf&lt;/I&gt;) until we are sure that the new version is correct.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;The consequence is that new classes can be tested by the developer, &lt;B&gt;&lt;I&gt;actually in the EXE&lt;/I&gt;&lt;/B&gt;, without re-compiling the code. All that is needed is an active entry in &lt;I&gt;wipclasses.dbf&lt;/I&gt; to override the current ‘production’ version.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Another consequence is that, in a team environment, each developer can have their own local &lt;I&gt;wipclasses&lt;/I&gt; table, and a read-only copy of the main &lt;I&gt;classes.dbf&lt;/I&gt;. That way when someone is developing a class they don’t affect everyone else’s code while they are de-bugging their changes.&amp;nbsp;Once a new class, or sub-class, is ready for production, all that needs to be done is to update the appropriate class library and (if needed) modify the entry classes table and the application begins using the new class immediately.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;The factory class is defined in code as a sub-class of the Session base class. This, as noted above, means that the factory can be instantiated in the application environment as a global object that maintains its own private datasession. The class includes a property (&lt;/FONT&gt;&lt;SPAN class=codeChar&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;lWIPTable&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;FONT face=Tahoma size=2&gt;) this serves as a flag that is set when the class is instantiated so that we don't have to keep testing for the presence of the WIP table. The &lt;I&gt;Init()&lt;/I&gt; method is responsible for setting up the metadata, and setting this flag.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;All of the real work is handled in the exposed &lt;I&gt;New() &lt;/I&gt;method. It accepts a key name, and up to five additional parameters which (if present) are passed directly to the &lt;I&gt;Init()&lt;/I&gt; of the object. If the object is instantiated correctly, a reference to the new object is returned to the client, otherwise a NULL value is handed back.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Note that the class determines, from the extension of the specified class library, whether to use &lt;I&gt;CREATEOBJECT()&lt;/I&gt; if the library has been explicitly declared, or &lt;I&gt;NEWOBJECT() &lt;/I&gt;if not. If the object is instantiated successfully, and there are settings defined as defaults in the Properties column, then the factory will attempt to set those values as well (see the &lt;I&gt;GetProperties()&lt;/I&gt; and &lt;I&gt;Str2Exp()&lt;/I&gt; methods) – this is, of course, in addition to any parameters that may be passed and handled by the class code directly. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So to create an instance of an object defined in the application source code all that has to be done is for the application to have access to the factory object and to call its &lt;I&gt;New()&lt;/I&gt; method with the key name. The return value will either be an object reference or NULL, and the client handles both possibilities: &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;TRY&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;loObject = oFactory.New( ‘Required_Object_KeyName’ )&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF ISNULL( loObject )&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ERROR “Unable to instantiate” + Required_Object_KeyName )&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;CATCH TO loErr&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;MESSAGEBOX( loErr.Message + “ in ” + loErr.Procedure, 16, “Factory Error” )&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;ENDTRY&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;You may be wondering why this does not create a dangling object reference. The reason is simply that the object is created as LOCAL inside the &lt;I&gt;Factory.New()&lt;/I&gt; method. So when the method completes, the local reference goes out of scope and is released, leaving the returned reference as the only one in existence.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;Summary&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;The implementation of the factory pattern illustrated here offers two major benefits. First, and most obviously, it avoids the issues that arise when class and library names are embedded directly in code. Second, it allows developers to work on, and test in a production environment, code that is still ‘work-in-progress’ without breaking anyone else’s version of the application. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The code for our factory class is here:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;
&lt;P class=bodytext&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;o:p&gt;&lt;FONT face="Courier New" size=2&gt;&lt;SPAN&gt;&lt;FONT face="Courier New"&gt;******************************************************************************************&lt;BR&gt;*** Program: Factory.prg&lt;BR&gt;*** Written by Andy Kramek &amp;amp; Marcia G. Akins &lt;BR&gt;*** Abstract: Abstract factory responsible for object instantiation&lt;BR&gt;*** Parameters: keyword from classes table to look up class info and up to 4 optional parameters&lt;BR&gt;*** Compiler.: Visual FoxPro 09.00.0000.3504 for Windows&lt;BR&gt;*****************************************************************************************&lt;BR&gt;DEFINE CLASS factory AS Session&lt;BR&gt;*** Give it a private data session so we can set exact ON&lt;BR&gt;*** so we can accurately use a SEEK into the classes table&lt;BR&gt;DataSession = 2&lt;BR&gt;lWIPTable = .F.&lt;BR&gt;******************************************************&lt;BR&gt;FUNCTION Init()&lt;BR&gt;******************************************************&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** This is a private data session so just force exact ON&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;SET EXACT ON&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Open the classes table&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF NOT USED( 'Classes' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;USE Classes AGAIN IN 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Open WipClasses if we have it available, and set property&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF NOT USED( 'WIPclasses' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF FILE( 'WIPclasses.dbf' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;USE WIPclasses AGAIN IN 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.lWIPTable = .T.&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.lWIPTable = .T.&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;ENDFUNC&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;****************************************************************************&lt;BR&gt;FUNCTION New( tcKey, tuParam1, tuParam2, tuParam3, tuParam4, tuParam5 )&lt;BR&gt;*****************************************************************************&lt;BR&gt;LOCAL lcLibType, lcCommand, lnParm, lnParmCount, loObject, llFound, lcKey, lnSelect, llCreate, lcProperties&lt;BR&gt;LOCAL lcLibrary, lcClassName&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Make sure we got a keyword&lt;BR&gt;IF EMPTY( tcKey )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN .NULL.&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Get the class information&lt;BR&gt;lcKey = UPPER( ALLTRIM( tcKey ) )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Check to see if the developers are using a local classes table&lt;BR&gt;*** to test work in progress. If there is one, use the information&lt;BR&gt;*** from the local table&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;if it is there. Only check the application&lt;BR&gt;*** classes table if the keyword can't be found in the local one&lt;BR&gt;lnSelect = SELECT()&lt;BR&gt;IF This.lWIPTable&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;SELECT WIPClasses&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;llFound = SEEK( lcKey, 'WIPclasses', 'cKey' ) AND WipClasses.lActive&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;*** Now check the application classes table if&lt;BR&gt;*** we need to&lt;BR&gt;IF NOT llFound&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;SELECT Classes&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;llFound = SEEK( lcKey, 'Classes', 'cKey' ) AND Classes.lActive&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;IF NOT llFound&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Make sure keyword was found in classes table&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN .NULL.&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Save pertinent info and force to upper case&lt;BR&gt;lcProperties = ALLTRIM( Properties )&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;lcLibrary = UPPER( ALLTRIM( cLibrary ) )&lt;BR&gt;lcClassName = UPPER( ALLTRIM( cClassName ) )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Is this class in a vcx or a prg?&lt;BR&gt;lcLibType = This.ChkLibType( lcLibrary )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;*** Make sure our class library has an extension&lt;BR&gt;*** Just in case one was not specified in Classes.dbf&lt;BR&gt;IF EMPTY( lcLibType ) &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN .NULL.&lt;BR&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lcLibrary = FORCEEXT( lcLibrary, lcLibType )&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Now, see if our prg or vcx has been set &lt;BR&gt;IF lcLibType = 'PRG'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;llCreate = ( lcLibrary $ SET( 'PROCEDURE' ) ) OR ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;( FORCEEXT( lcLibrary, 'FXP' ) $ SET( 'PROCEDURE' ) )&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;llCreate = lcLibrary $ SET( 'CLASSLIB' )&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;IF llCreate&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lcCommand = 'CreateObject( "' + lcClassName + '"'&lt;BR&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lcCommand = 'NewObject( "' + lcClassName + '", "' + lcLibrary + '"'&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Now tack the parameters on to the end of the command&lt;BR&gt;*** if any were passed&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;lnParmCount = PCOUNT() - 1&lt;BR&gt;IF&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lnParmCount &amp;gt; 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Only add the third parameter if we are using NewObject&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF NOT llCreate&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcCommand = lcCommand + ', ""'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FOR lnParm = 1 TO&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lnParmCount&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcCommand = lcCommand + ', tuParam' + TRANSFORM( lnParm )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDFOR&lt;BR&gt;ENDIF&lt;BR&gt;lcCommand = lcCommand + ' )'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Go ahead and instantiate the object&lt;BR&gt;loObject = &amp;amp;lcCommand&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Now see if we have some properties to set&lt;BR&gt;IF NOT EMPTY( lcProperties )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** First make sure we have an object&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF VARTYPE( loObject ) = 'O'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;This.GetProperties( loObject, lcProperties )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;SELECT ( lnSelect )&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;RETURN loObject&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;ENDFUNC&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*****************************************************************************&lt;BR&gt;PROTECTED FUNCTION GetProperties( toObject, tcProperties )&lt;BR&gt;*****************************************************************************&lt;BR&gt;LOCAL lnTotal, laProps[ 1 ], lnCnt, lcPropName, lcPropValue, lcType, loItem, luValue&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** get all the attribute/value pairs in the memo field into a single array element&lt;BR&gt;lnTotal = ALINES( laProps, tcProperties )&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;*** And process each pair in the array&lt;BR&gt;FOR lnCnt = 1 TO lnTotal&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Get the name of the property&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lcPropName = ALLTRIM( GETWORDNUM( laProps[ lnCnt ], 1, '=' ) )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lcPropValue = ALLTRIM( GETWORDNUM( laProps[ lnCnt ], 2, '=' ) ) &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Make sure it is a property on the object&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF PEMSTATUS( toObject, lcPropName, 5 )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** See if we can get its data type&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;loItem = 'toObject.' + lcPropName&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcType = TYPE( loItem )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Now get back the value in the appropriate data type&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luValue = This.Str2Exp( lcPropValue, lcType )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;amp;loItem = luValue&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;ENDFOR&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;********************************************************************************&lt;BR&gt;PROTECTED FUNCTION Str2Exp( tcExp, tcType )&lt;BR&gt;********************************************************************************&lt;BR&gt;LOCAL lcExp, luRetVal, lcType, lcStr&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Verify parameters&lt;BR&gt;IF VARTYPE( tcExp ) # 'C'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ASSERT .F. MESSAGE TRANSFORM( tcExp ) + ' is NOT a character expression and you MUST pass a character expression to Str2Exp!'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN tcExp&lt;BR&gt;ENDIF&lt;BR&gt;IF EMPTY( tcType )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ASSERT .F. MESSAGE 'You MUST pass a data type to Str2Exp!'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN tcExp&lt;BR&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** If no type passed -- map to expression type&lt;BR&gt;lcType = UPPER( ALLTRIM( tcType ) )&lt;BR&gt;*** Remove any NULL characters, and leading/trailing spaces&lt;BR&gt;lcExp = CHRTRAN( ALLTRIM( tcExp ), CHR( 0 ), '' )&lt;BR&gt;*** Convert from Character to the correct type&lt;BR&gt;DO CASE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;*** Integers&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;CASE INLIST( lcType, 'I', 'N' ) AND INT( VAL( lcExp ) ) == VAL( lcExp ) &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = INT( VAL( lcExp ) )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Other Numeric &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;CASE INLIST( lcType, 'N', 'B' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = VAL( lcExp )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Currency&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;CASE lcType = "Y"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = NTOM( VAL( lcExp ))&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Character or memo&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;CASE INLIST( lcType, 'C', 'M' ) &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Remove delimiting marks if present.&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF INLIST( LEFT(lcExp,1), CHR(91), CHR(34), CHR(39))&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** We begin with a delimiter&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcExp = SUBSTR( lcExp, 2 )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** So we should end with a delimiter&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF INLIST( RIGHT(lcExp,1), CHR(93), CHR(34), CHR(39))&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcExp = LEFT( lcExp, LEN( lcExp )- 1 )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = lcExp&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Logical&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;CASE lcType = 'L'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = IIF( !EMPTY( CHRTRAN( lcExp, 'Ff0.', "" ) ), .T., .F.)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Date&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;CASE lcType = 'D' &amp;amp;&amp;amp; Date&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Check for separators in the string&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF CHRTRAN( lcExp, "/.-", "" ) == lcExp&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** We are in yyyymmdd format&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = LEFT( lcExp, 4) + "," + SUBSTR( lcExp, 5, 2 ) + "," + RIGHT( lcExp, 2)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = DATE( &amp;amp;lcStr )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** We are in DTOC() format&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = CTOD( lcExp )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** DateTime&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;CASE lcType = 'T' &amp;amp;&amp;amp; DateTime &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Check for date separators in the string&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF CHRTRAN( lcExp, "/.-", "" ) == lcExp&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** No separators so we have something other than TTOC() format&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF LEN( lcExp ) &amp;gt; 8&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** This one must be in yyyymmddhhmmss format&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** So get the date part first&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = LEFT( lcExp, 4) + "," + SUBSTR( lcExp, 5, 2 ) + "," + SUBSTR( lcExp, 7, 2)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** and convert to the correct date string format&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = DTOC( DATE( &amp;amp;lcStr ))&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Now tack on the hours part&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = lcStr + " " + SUBSTR( lcExp, 9, 2 )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Minutes&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF LEN( lcExp ) &amp;gt; 10&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = lcStr + ":" + SUBSTR( lcExp, 11, 2 )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = lcStr + ":00"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** Seconds&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;IF LEN( lcExp ) &amp;gt; 12&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = lcStr + ":" + SUBSTR( lcExp, 13, 2 )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = lcStr + ":00"&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = CTOT( lcStr )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** This must be a date in yyyymmdd format which we want to force to DateTime format&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcStr = LEFT( lcExp, 4) + "," + SUBSTR( lcExp, 5, 2 ) + "," + RIGHT( lcExp, 2)&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = DTOT( DATE( &amp;amp;lcStr )) &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** We are already in TTOC() format&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = CTOT( lcExp )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;OTHERWISE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;*** We have an invalid combination of value and data type&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;MESSAGEBOX( "Cannot convert " + lcExp + " to Data Type " + tcType, 16, "Conversion Failed " )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;luRetVal = lcExp&lt;BR&gt;ENDCASE&lt;BR&gt;*** Return value as Data Type&lt;BR&gt;RETURN luRetVal&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*****************************************************************************&lt;BR&gt;PROTECTED FUNCTION ChkLibType( tcLibrary )&lt;BR&gt;*****************************************************************************&lt;BR&gt;LOCAL lcLibType&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;*** Checks for file extension on library name, and&lt;BR&gt;*** figures out what it should be if not supplied&lt;BR&gt;lcLibType = UPPER( JUSTEXT( tcLibrary ) )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;IF NOT EMPTY( lcLibType ) &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lcLibType = IIF( FILE( tcLibrary ), lcLibType, '' )&lt;BR&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** See if we have a vcx here&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;lcLibType = IIF( FILE( FORCEEXT( tcLibrary,'VCX' ) ), 'VCX', '' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF EMPTY ( lcLibType )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;lcLibType = IIF( FILE( FORCEEXT( tcLibrary,'PRG' ) ), 'PRG', '' )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;ENDIF&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;BR&gt;RETURN lcLibType&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;BR&gt;ENDDEFINE&lt;/FONT&gt;&lt;BR&gt;&lt;BR&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/o:p&gt;
&lt;P&gt;&lt;/P&gt;&lt;A HREF="/photos/andykramek/picture3872.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/4509/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/4509/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/picture4509.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/picture4510.aspx" target=_blank&gt;&lt;/A&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=4508" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1062.aspx">VFP How-To</category></item><item><title>SW Fox, An Endangered Species I fear!</title><link>http://weblogs.foxite.com/andykramek/archive/2007/06/24/4172.aspx</link><pubDate>Sun, 24 Jun 2007 10:30:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:4172</guid><dc:creator>andykr</dc:creator><slash:comments>6</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/4172.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=4172</wfw:commentRss><description>&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Marcia and I have just returned from Prague, where we were lucky enough to be among the speakers for the third time. This was, and is, a &lt;I&gt;great&lt;/I&gt; conference and this year had over 300 attendees. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;However, the first time we went there (in 2002) they had 600+. Why do I mention this? &lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Well, I am told that Advisor Devcon this year had about 35 attendees for the VFP Track (contrast that with Advisor DevCon 2000 in Miami where there were over 1200, and with Orlando in 1998 where there were more than 2000). &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;I also know that last year's Southwest Fox in Phoenix had only just over 100 attendees.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;It does seem to be a general phenomenon across the world that fewer and fewer people are going to IT Conferences these days and there are probably several reasons for this. Obvious ones are budgetary constraints and&amp;nbsp;increased pressure on deadlines that reduces 'training' time.&amp;nbsp;However, &amp;nbsp;I also think that the ever-expanding scope of freely available advice and help on line has, for some people (especially those not directly involved), reduced the perceived value of conferences. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;However I totally disagree with that viewpoint and consider that conferences offer three huge benefits that cannot be obtained in any other way:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;[1] Networking. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;In our business, as in any other, "what you know" is important, but often "who you know" is at least as important. How can you know someone unless you meet with them, talk with them and spend time with them? In some ways the actual conference sessions are the least important aspect of a conference. It is the opportunity to meet others who live in the same world as you do and who do the same things that you do, to talk with them and discuss common issues and problems that adds significant value. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Of course you also have to make the effort of going and talking to people! I have never understood people who go to a conference, attend the sessions and then disappear back to their hotel room – they are missing the most important part – the evening chat sessions in lobby, or the bar, or wherever the VFP crowd are congregating&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;[2] Broadening Horizons. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;We each live our professional lives immersed in the day to day issues with which we are concerned. How often do we take the time out to investigate some new facet of VFP? Or to look at some programming technique or issue for which we have no immediate use? Not very often I would wager. This is the shortcoming that on-line forums and help sites cannot address. They are 'problem oriented' in that when a question is asked, the response is an answer to that question. What you do not get is the answers to unasked questions or general information. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;For example, in Prague this year I attended a session given by Alan Griver on "VB Futures". This is something that I would not normally spend time researching, or even reading casually about, but it was an immensely interesting and revealing session and I learned a lot. Will I use it '&lt;I&gt;today&lt;/I&gt;'? No! Is it worth knowing about? Definitely! &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;[3] Learning New Skills/Approaches&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;One of the most significant benefits that I see from conferences is the presentation of alternatives. VFP is an immensely rich and powerful language and no-one really knows it all. (There are over 1500 Commands, Functions, Properties, Events and Methods, and probably 10 times that number of variations on them). It is almost an axiom that there are at least two ways (and usually more) of doing anything in VFP. What most of us (including myself) do is learn one and stick with it. At conferences I get a chance to see other approaches to problems, other techniques for using VFP and its tools and other people's solutions to problems