Egg or Chicken, Properties or Methods?
In Visual FoxPro you can use expressions to initialize properties. These expressions are evaluated only once when you first load a class into memory. The result is stored in the class object. To create an expression for a property, you can either enter the equals sign in the Properties Windows followed by the expression or you press the "fx" button next to the text box on the Properties Window.
I've shown various times that these expressions may include THIS. The reference points to the class object, not the object that you are creating. In fact, THIS doesn't even point to an object of the same class. If you only access properties of the class object, you won't notice a difference.
If you attempt to call a method, though, you need to be aware that properties are evaluated before methods. So what does this mean? Create a new class "Level1" based on Form and store it in Level1.VCX. Add a method "CallMethod" with the following code:
? "Called ", This.Class
Now add a property nTest and assign the following expression:
In the Command Window enter:
ox = NewObject("Level1","Level1")
You notice two things. The screen is empty and nTest is .F., not 2. The reason for this is that by the time the expression is evaluated, the method code has not yet been attached to the method. CallMethod() exists, but is empty. Now let's drive this one step further. Create a program ShowInfo.prg with the following content
Create a new class Level2 that inherits from Level1. Store the new class in Level2.vcx. Override CallMethod with this code:
? "Subclass called ", This.Class
Now switch to the Command Window and enter:
ox = NewObject("Level2","Level2")
This time you can see the following output:
ShowInfo is clearly called when the class is loaded. The method call also works, at least kind of. Instead of executing the code in the Level2 class it now executes the code from the Level1 class that hasn't been executed when you instantiated Level1.
What happened is that when you instantiate Level2, Visual FoxPro first creates a class object for Level1. This happens in two steps. First, all properties are evaluated. Then all methods are added. As you requested Level2, Visual FoxPro creates the class object for Level2 next. VFP does so by creating a copy of the Level1 class object including the method code. As before, properties are evaluated first. At this time, CallMethod still contains the code that has been copied over from the class object of Level1.
Only after all properties have been evaluated, Visual FoxPro adds the method code to the Level2 class object. If you create public variable in ShowInfo.prg and store toRef in that variable, you get a reference to the class object. When you execute CallMethod() on this global object after creating an instance, you get indeed the sub-classed code.
So remember: Properties exist before methods do. If you don't want to create these classes yourself, you can download them here.
TRANSLATE() is the PL/SQL function that provides the same functionality as CHRTRAN() in Visual FoxPro. The function searches the first string and replaces every character in the second one with the corresponding value from the third string:
SELECT TRANSLATE('12341234','23','XZ') FROM DUAL;
Like CHRTRAN in Visual FoxPro you can use TRANSLATE() to remove characters from a string. When the third string is shorter than the second one, all characters in the second string that do not have a corresponding position in the third string are removed. There's a major difference to Visual FoxPro, though:
SELECT TRANSLATE('12345','24','') FROM DUAL
You might expect this expression to return '135'. However, Oracle returns NULL. How comes that? Oracle doesn't support empty strings. Empty strings, like the third parameter, automatically become NULL in Oracle. Yet, if any of the parameters for TRANSLATE() is NULL, so is the result. To get around this issue, you can use the same trick we used with IF lines in DOS batch files years ago:
SELECT TRANSLATE('12345','.24','.') FROM DUAL
This expression replaces periods with periods and removes '2' and '4'. By adding an additional character the third parameter doesn't become NULL and you get the result you expect.
Being fooled by Oracle
PL/SQL is a programming language that is quite close to Visual FoxPro. Many string functions have the same syntax as in Visual FoxPro, such as SUBSTR() or are pretty close like PADR() that becomes RPAD() in Oracle. However, with concatenation there's a huge difference in Oracle. To combine strings you use the "||" operator instead of "+". That would be fine, if the plus operator wouldn't work with strings, too:
SELECT '23'+'45' FROM DUAL;
The result, however, is 68 as a numeric value. When you work with strings and use the plus operator, Oracle converts the string to a number before adding both values. If you pass an expression that Oracle can't interpret as a number, you receive an error message:
SELECT 'A'+'B' FROM DUAL;
ORA-00900: invalid SQL statement
In a real world application, though, you won't use such values hard coded in a SELECT statement. Rather, you have a complex PL/SQL stored procedure and the error message comes up in a huge combined INSERT FROM SELECT statement. If you are used to a language like Visual FoxPro that uses plus for strings, too, you might not immediately spot this error.
The scope of CREATEOBJECT
Sometimes one gains insights that are absolutely useless. One such piece is the scope of
CREATEOBJECT(). The first time you instantiate an object from a class, Visual FoxPro creates
a template object with all the properties values that the class defines. If the class definition
contains an expression for one of the properties, Visual FoxPro evaluates it at this time once.
For any further instance, Visual FoxPro uses the pre-evaluated expression from the class object.
Now the useless part of this article: All class properties are evaluated in the scope of the procedure
or method that instantiates the object. This means, that in the class property expressions you have
access to every local variable in that procedure:
Local lcVar, loRef
lcVar = "Hi"
loRef = CreateObject("Test")
Define Class Test as Custom
cTest = m.lcVar
So much for keeping this strictly technical...
Craig Berntson and Ted Roche tagged me in their blogs.
Which means I'm now participating in a cocktail party game initiated by
Jeff Pulver and I don't even know him except
for five things. So, here are the five things about me you didn't (want to) know before:
- I collect Walt Disney comics. From the money I made with my first FoxPro program
I bought the first comic in my collection. Today I've got a little less than 1,000 issues.
- In school I was the last boy in my class that got a computer.
- I got my driver's license in 2004, but still prefer trains over cars.
- I love cooking. It's hard to understand why people buy food where you need a PhD in chemistry to understand
the list of ingredients.
- Unlike many other programmers I never really learned to play an instrument.
As I live in the real world rather than a blogosphere I don't have anyone to tag.
Binding to the Valid event
As with so many things in Visual FoxPro, binding to the Valid event isn't as straight forward
as one may wish to. When binding to a control in a grid, Visual FoxPro calls the delegate object
only when the grid control contains user defined code in the Valid event at some point in the
class hierarchy. This code doesn't have to do anything and can be a plain comment. Visual FoxPro
needs the user-defined code as a binding place, so its mere existence is sufficient.
If you try to bind to a textbox's Valid event using the default object Text1 or a regular
textbox object without any special code in the Valid event, your code is never called. This
behavior is actually documented (well, hidden) in the Remarks section of BindEvent:
"Certain events such as When and Valid require code in the event for it to occur."
The following sample demonstrates this behavior. No matter how you navigate in the grid
or what values you change, the only Valid event you ever see is the one from column 2:
* Binding to the Valid event of a grid control is only
* possible if you use a subclassed control with code in
* the valid event.
Create Cursor Test (cField1 C(10), cField2 C(10))
Insert into Test Values ("one","two")
Insert into Test Values ("three","four")
goForm = CreateObject("Form")
goForm.grd.Visible = .T.
goForm.Left = 200
Define Class MyGrid as Grid
ColumnCount = 2
This.Columns(2).Text1.Visible = .T.
This.Columns(1).Text1, "Valid", ;
This, "ColumnValid" ;
This.Columns(2).Text1, "Valid", ;
This, "ColumnValid" ;
Define Class MyTextbox as TextBox