Wiki Home

VFP Gotchas OOP


Namespace: VFP
Idiosyncrasies in VFP that may surprise the uninitiated.
Also see VFP Gotchas

DODEFAULT and NODEFAULT seem to behave differently if they are in a class that is a subclass of a VFP baseclass. I think there are 4 levels that have different behavior:
0 = no subclassing, just putting code in the instance of a VFP baseclass.
1 = code in a subclass of a VFP baseclass.
2 = code in a subclass of 1
3-n = code in a subclass of 2 or 3

There may be differences depending on empty methods too. I played around with this enough to get a headache, and hope that someone could define it better.

I hope so too. Here you have defined the possible scenarios but the actual behaviour differences remain unstated. -- Jim Nelson
Linky! DODEFAULT Or NODEFAULT -- Peter Crabtree
Question, is there a difference between 0 and 1 above? AFAIK, under the covers, when you add code to an instance of a baseclass, you're, for all practical purposes, adding code to a subclass. -- Mike Helland
I don't think there's a difference, in practice, other than a couple of slightly different behaviors because of having several controls with the same class in the same container. You are essentially subclassing when you are creating an instance with code - the only difference is that you can't subclass it. Someone please correct me if I'm wrong. -- Peter Crabtree

I contend that this next one, while interesting, is not a "vfp gotcha" per se, it's a common issue in subclassing. If someone else agrees, feel free to remove this from this topic. - ?lc

Not exactly a "VFP gotcha", not exactly not. As there is no way to tell for sure if a parameter has been passed, consider the following:

CreateObject("myTestClass", 1, 2, 3, 4)
CreateObject("myTestClass", 1, 2, 3, .F.)
CreateObject("myTestClass", , , , 4)
CreateObject("myTestClass", , .F., , 4)

DEFINE CLASS myTestClass AS Custom
	FUNCTION Init(tvParam1, tvParam2, tvParam3, tvParam4)
		LOCAL lcText
		lcText = Space(0)
		SET TEXTMERGE TO MEMVAR lcText NOSHOW
		SET TEXTMERGE DELIMITERS TO "{{","}}"
		\\{{tvParam1}}
		\{{tvParam2}}
		\{{tvParam3}}
		\{{tvParam4}}
		\{{Pcount()}} Parameters passed!
		SET TEXTMERGE TO
		MessageBox(lcText)
	ENDFUNC
ENDDEFINE


You can never really tell what parameters have been passed in VFP, while in other languages, you can. Not totally specific to VFP, but specific enough. -- Peter Crabtree

Optional parameters need to be addressed when subclassing. This is a hard one to demonstrate, but when you get bit by it it becomes crystal clear.

In the following code, the b1Cust class just passes the tcParm2 parameter through to the aCust class, but the aCust class is checking to see how many parameters were passed. the b2Cust class addresses this issue by only passing through parameters that were actually passed in in the first place.

x = CreateObject( "aCust", "hello" )
? x.cProp1+" "+x.cProp2
x = CreateObject( "b1Cust", "hi" )
? x.cProp1+" "+x.cProp2
x = CreateObject( "b2Cust", "hi" )
? x.cProp1+" "+x.cProp2
x = CreateObject( "b2Cust", "hi", "there" )
? x.cProp1+" "+x.cProp2

Return

Define Class aCust As TextBox
	cProp1 = ""
	cProp2 = "World"
	Procedure Init( tcParm1, tcParm2 )
		This.cProp1 = tcParm1
		If Pcount()=2
			This.cProp2 = tcParm2
		Endif
		Return .T.
	Endproc
Enddefine

Define Class b1Cust As aCust
	Procedure Init( tcParm1, tcParm2 )
		tcParm1 = proper( tcParm1 )
		DoDefault( tcParm1, tcParm2 )
		Return .T.
	Endproc
EndDefine

Define Class b2Cust As aCust
	Procedure Init( tcParm1, tcParm2 )
		tcParm1 = proper( tcParm1 )
		DO case
			Case Pcount()=1
				DoDefault( tcParm1 )
			Case Pcount()=2
				DoDefault( tcParm1, tcParm2 )
		EndCase
		Return .T.
	Endproc
EndDefine



The answer for this is not to use Pcount() or Parameters() at all. This requires a design desision, and isn't really a solution for pre-existing programs (not an easy solution, anyway), but is great for new programs. You should use Vartype() to check if a variable's been passed or not. This of course means that you can't use .F. as a valid parameter, or it has to be the default.

This program (I've tried to maintain the same style) has a few improvements: first, it's simpler to subclass. All you need is a DoDefault( tcParam1, tcParam2 ), and it'll work, regaurdless of which parameters have been passed.
Second, if parameters get passed by referance, they won't get messed up (tcParm1 = proper( tcParm1 ) could be a problem).
Third, it doesn't have the redundant RETURN .T. statements (VFP automatically RETURNs .T. if there was no RETURN in the code).
And finally, if the superclass code RETURNs .F., the subclasses will too.

x = CreateObject( "aCust", "hello" )
? x.cProp1+" "+x.cProp2
x = CreateObject( "b1Cust", "hi" )
? x.cProp1+" "+x.cProp2
x = CreateObject( "b2Cust", "hi" )
? x.cProp1+" "+x.cProp2
x = CreateObject( "b2Cust", "hi", "there" )
? x.cProp1+" "+x.cProp2

Return

Define Class aCust As TextBox
	cProp1 = ""
	cProp2 = "World"
	Procedure Init( tcParm1, tcParm2 )
		This.cProp1 = tcParm1
		If Vartype( tcParm2 ) == "C"
			This.cProp2 = tcParm2
		Endif
	Endproc
Enddefine

Define Class b1Cust As aCust
	Procedure Init( tcParm1, tcParm2 )
		Return DoDefault( proper( tcParm1 ), tcParm2 )
	Endproc
EndDefine

Define Class b2Cust As aCust
	Procedure Init( tcParm1, tcParm2 )
		Return DoDefault( proper( tcParm1 ), tcParm2 )
	Endproc
EndDefine

-- Peter Crabtree

You can certainly use PCOUNT. Just do this...

DEFINE CLASS b2Cust AS aCust
  PROCEDURE Init(m.tcParm1, m.tcParm2)
    LOCAL m.lcParms
    m.lcParms = PassParms("m.tcParm1, m.tcParm2",PCOUNT())
    RETURN DODEFAULT(&lcParms.)
  ENDPROC
ENDDEFINE


PASSPARMS.PRG
LPARAMETERS m.tcParmlist, m.tnItems
RETURN LEFT(m.tcParmlist + ",", AT(",", m.tcParmlist, m.tnItems)-1)
-- Mike Yearwood

Certainly you can; but at the cost of an additional variable, at least one extra line of code, and the (relatively huge (compared to a method call)) overhead cost of a macro substitution. -- Peter Crabtree

I don't consider a variable or even a few lines of code any kind of 'cost', as long as they are not redundant. However, I'll agree the macro should be avoided. It's worse than a method call which is worse than a UDF call. It can be done like this...

DEFINE CLASS b2Cust AS aCust
  PROCEDURE Init(m.tcParm1, m.tcParm2)
    RETURN EVALUATE("DODEFAULT(" + PassParms("m.tcParm1, m.tcParm2",PCOUNT()) + ")")
  ENDPROC
ENDDEFINE
-- Mike Yearwood

... which is better, but still significantly slower and harder to read than:
RETURN DoDefault(m.tcParm1, m.tcParm2)
Instead of just a method call, you've got a method call on top of a function, a UDF, three string concatinations, and the real cost, an Evaluate() call, just because you want your method to have three behaviors due to the value of a boolean argument (which makes sense in some contexts, but sometimes you have to live with the limitations of the language, to avoid ugly kludges like this) -- Peter Crabtree
Since you can't be sure there are no PCOUNTs in the class hierarchy, it is risky to do it any other way. I'll take "ugly", which is simply cosmetic and therefore irrelevant to me, over risk any time. It's the DO CASE construct that is ugly with the cost of multiple lines of extraneous code. -- Mike Yearwood
Not sure if this is OOP or language...

Passing parameters to a form.

You can do this:
DO FORM FOO WITH "ABC"
and in FOO.init()
lParameters tcID
locate for cID = tcID

Unfortunately, it would be better if you could pass it to the form.load(), but it doesn't go there - it goes to init.

perhaps with VFP8's late binding capabilits, this won't be such a big problem anymore.
Category Needs Refactoring
( Topic last updated: 2006.02.10 10:27:03 AM )