by Dan Howard
A very common source of confusion in Visual dBASE is the use of properties over variables. In this article, I'll attempt to clarify the dos and don'ts of variable usage.
For the purpose of this article, when I use the term function, I mean a procedure, function or method. When I use the term property I'll be generally referring to a custom property.
Also, most of the code samples were written in dBASE 7.01 but they'll need only minor modifications to run under 5.7.
“Variables are named locations in memory where you store data values: strings, numbers, logical values, dates, nulls, object references, function pointers, and codeblocks. You assign each of these values a name so that you can later retrieve them or change them. You can use these values to store user input, perform calculations, do comparisons, define values that are used as parameters for other statements, and much more.”
To add to this: Variables are declared and used in procedures, functions and methods. Objects themselves don't use variables - only their methods do.
Well, actually objects can use variables but they shouldn't. I hope by the end of this article you'll understand why.
There are 4 basic types of variables: public, private static and local. Each type has it's own scope and lifetime.Declaring Variables
The following table summarizes the variable types and their different scopes and lifetimes. I also include whether or not you can use the macro ( & ) operator with them (See Macros for more information) :
- Scope: The variable scope means where the variable can be accessed from. Variables are either scoped to the entire application or only to the function that they were declared in.*
- Lifetime: The variable lifetime means how long the variable will keep its value. Variables can stay alive either throughout your application or only in the function that they were declared in.
[* Note: The exception is the PRIVATE variable which is also available in any lower level sub-routines. We'll see an example of this in the Gotcha section.]
Type Scope Lifetime Macro public Available to entire application Until end of application Yes private Available only in the function where it was declared and any sub-functions. Until end of function Yes static Available only in the function where it was declared. Until end of application No local Available only in the function where it was declared. Until end of function No
It's usually a good idea to declare any variables that you use. Visual dBASE won't complain if you don't but the default will be PRIVATE which may not always be what you want. This is how I normally declare my variables:Using Variableslocal a,b,c store 0 to a,b,cThe reason I do it this way is so I can take advantage of the := operator that was introduced with Visual dBASE 7. The := is an assignment only operator. Once your variables are declared and assigned a value you can use := exclusively to prevent the accidental creation of new variables. This works for properties too.
Note that it doesn't matter what you initially store to the variable. As long as you put something (even NULL) into the variable, you can use the := operator.
Also, unlike most languages, dBASE allows you to declare variables anywhere inside your functions. For example, the following is perfectly valid:Function MyVariables local j for j = 1 to 10 ?j next j private c c = "dan" ?c returnAlthough dBASE won't complain about this, you should generally declare your variables at the top of the function because you'll have an easier time debugging your applications.
Here are examples of each variable type and how they are used:Macros and Indirection
Locals are probably the most common type of variable that you would use. Locals can be used for anything that you would use a variable for except for the macro (&) operator and the type() function.Function MyFunction local j store 0 to jprivate k store 0 to k?type("j") // Will always return "U" ?type("k") // Will return "N" returnPrivate
I generally reserve privates for cases where I need to use the macro operator or the type() function. This is simply my personal preference. You can certainly use privates in any circumstance where locals are used but you should be aware that those variables could be changed by a lower level sub-routine. For me, changing a variable in a function where it was not declared in is bad programming practice because it can make debugging more difficult. An example of where I would use privates would be:Function Pushbutton1_onClick private o,m_macro m_macro = "form.entryfield1" o = &m_macro. if type("o.dataLink") = "O" ?"The datalink to this control is a field object." endif returnIn fact, you can avoid needing privates for the type() function by using this little function instead:// Call this function like the type() function except don't use quotes. e.g. // if LocalType(o.dataLink) = "O" // ?"The datalink to this control is a field object." // endifFunction LocalType param xValue return type('xValue')So really you only need privates for macros.
Static variables are probably the least common of the variable types. They are a hybrid of publics and locals. Like locals, they can only be used in the function that they were declared in but they are like publics in that they retain their values until you quit dBASE (or do a CLEAR MEMORY). Look up the stopwatch example in the on line help for an idea on their use (see STATIC in the help index). Statics are interesting in that they are the only variable which can have an in-line assignment. That means that they can be declared and assigned at the same time. e.g.// Increment a counter every time I get called. Function HitCounter static n = 0 // This line will only run the 1st time this function is called. n++ // or n = n + 1 for dBASE 5.x return ( n )Statics are useful in that they are only accessible by their function. This feature lets you use statics for variables that you want to ensure are initialized correctly. e.g.// Function INIFilename // Returns the current INI file in use or sets // the current INI file but only if the file // already exists. Function INIFileName( cFile ) static cIniFile = '' // If I was passed a parameter if argcount() = 1 if not file( cFile ) msgbox('Cannot set ' + cFile + ', it doesn't exist!') else cIniFile := cFile endif endif return ( cIniFile )Statics are not all that useful in an environment like dBASE since much of their functionality is available through OOP techniques. I won't fault you if you don't use them. ;-)
Publics are variables which are global. You can reference them from any routine in your application. They are declared like this:public a a = 10Publics appear to be the most controversial of the variable types. There has been much discussion in the newsgroups concerning whether to use them or not. Publics are useful for application specific information e.g.
One of the problems with publics though is that you can declare them in any routine. This can make debugging your code very difficult. Because of this you should only declare publics in your main program.
- a reference to the Ini object
- version information
- user information
- language settings
Instead of using public variables you can also use the _app object. The _app object is a Visual dBASE object which represents the current running application. Since it's global, it makes a good place holder for values that you want to be available everywhere. To use the _app object, simply assign the values to it like this:_app.MyValue = 10// or_app.Ini = New Ini("MyIni.INI")Publics are absolutely not useful for circumventing the object model. I'll get to this in the properties section.
Macros are strings of programming code that can be compiled and executed at runtime. e.g.Variables and Fieldsm_macro = "form.entryfield1" o = &m_macro. // this is translated on the fly by dBASE to: // o = form.entyfield1In many cases we used macros for dBASE commands like:m_macro = "customer.dbf" use &m_macro.We can also do these things using the indirection () operators. When you put these brackets around a variable, you're telling dBASE to give you it's indirect value. Here's an example:**** * Macro Method **** private cTable cTable = GetFile( "*.dbf" ) if not empty(cTable) use &cTable. browse endif**** * Indirection Method **** local cTable cTable = GetFile( "*.dbf" ) if not empty(cTable) use ( cTable ) browse endifUsing indirection has a few advantages. Namely that you can do these things with local variables which will shave a millisecond or 2 off of your execution time.
Indirection will not work in all places where you would use a macro but it does work with commands like: copy to, append from, use, cd, md, etc.
Consider the following code:Parametersprivate first_name, last_name first_name = "Dan" last_name = "Howard" ?"Values to save: ", first_name, last_name use contacts append blank replace first_name with first_name, last_name with last_name ?"results:",first_name, last_name useWhat do you think the output will be? The answer is that the first_name and last_name fields will be left blank! The reason is that when you open up a table dBASE creates variables representing the fields in the table. The fields will take precedence. One work around is to name your variables differently from the field names. This may not always be feasible. A better approach is to use the alias ( -> ) operator. Using this operator tells dBASE explicitly which variable or field you're talking about. There are 2 ways to improve the above example:replace first_name with M->first_name, last_name with M->last_name // An even better way... replace CONTACTS->first_name with M->first_name, CONTACTS->last_name with M->last_nameBoth methods will work but I like the second one is better because it avoids any confusion as to what's a variable and what's a field. Notice that M is a special alias which tells dBASE that it refers to a memory variable.
Parameters are variables that you pass to a function. There are two ways to declare parameters for a function:What are Properties?Function MyFunction parameters a,b,cOrFunction MyFunction(a,b,c)The difference in the two styles of declarations is that in the first example the parameters will be created as private - in the second example they'll be local.
There are also 2 ways to call your function with parameters:MyFunction(a,b,c)OrDO MyFunction WITH a,b,cIn addition, there are 2 ways that parameters are passed to a function: by reference or by value.
From the On-line Help:
- If you pass variables by reference, the called function has direct access to the variable. Its actions can change (overwrite) the value in that variable. Pass variables by reference if you want the called function to manipulate the values stored in the variables it receives as parameters.
- If you pass variables by value, the called function gets a copy of the value contained in the variable. Its actions can't change the contents of the variable itself. Pass variables by value if you want the called function to use the values in the variables without changing their values - on purpose or by accident - in the calling subroutine.Note: The default behavior of dBASE is to pass by reference. If you're coming to dBASE from another language, this can really bite you!Unless you want a subroutine to be able to modify your parameters you should always pass by value. Since this is not the default dBASE behavior, you have to use the indirection operators to accomplish this. e.g.MyFunction( (a), (b), (c) )Another way to prevent accidental changes to your parameters is to always make a copy of them in the called subroutine. e.g.do MyFunction with a,b,c function MyFunction(a,b,c) local x,y,zx = a y = b z = c // Now you can modify x,y,z to your hearts content! returnNote: Objects and Arrays are always passed by reference.
Sometimes it's desirable to pass by reference. Let's say you want a function which will double whatever value you send to it. Easy enough to write one.n = 10 Double(n) ?nFunction Double(x) x = x * 2 returnBy the way, did you know that you can easily write functions which take a variable number of parameters? It's very easy to do with the argcount() and argvector() functions. Copy & paste the following code and experiment with passing any number of parameters you want to the function:// Visual dBASE 7.x VersionMultiParam("45",17,true,date()) Function MultiParam local j, n private xParam n = argcount() ?"You passed " + n + " parameters to me!" ?"They're values are:" for j = 1 to n xParam = argvector(j) ?"Parameter: " + j + " is " + xParam + " - it's type is: " + type('xParam') next j return* Visual dBASE 5.x VersionMultiParam("45",17,.t.,date()) Function MultiParam local j, n private xParam n = argcount() ?"You passed " + rtrim(ltrim(str(n))) + " parameters to me!" ?"They're values are:" for j = 1 to n xParam = argvector(j) ?"Parameter: " + rtrim(ltrim(str(j))) + " is " ??xParam ??" - it's type is: " + type('xParam') next j return NULLVery nifty feature - don't you think? This also works within a class/endclass.m = new MultiParam("45",17,true,date()) class MultiParam local j, n private xParam n = argcount() ?"You passed " + n + " parameters to me!" ?"They're values are:" for j = 1 to n xParam = argvector(j) ?"Parameter: " + j + " is " + xParam + " - it's type is: " + type('xParam') next j endclass
After covering so much on variables it's time to examine properties and see how they differ in use and functionality from variables.Creating Properties
For the sake of this article, when I use the term property I mean custom property. Custom properties are the properties that you add to any of the built-in objects in Visual dBASE. Here's a simple example:
Class MyForm of Formthis.isOpen = false Function OnOpen this.isOpen := true return Function OnClose this.isOpen := false return endclassThe question though is: Why use properties at all? The answer is because of scoping and lifetime.
Properties act pretty much the same as variables do except that they are scoped to an object and not to a function. They will exist and be accessible as long as the object exists. In the above example, the IsOpen property does not die at the end of the OnOpen method. It's stays alive until the form itself is released from memory. This is a very important concept to understand since most of dBASE and Windows itself is object oriented.
The advantage of a property is that you can create multiple objects and each will have it's own set of data so you never have to worry about one value overwriting the other. e.g.f1 = new MyForm() f2 = new MyForm() f2.Open() ?f1.isOpen ?f2.isOpenBoth objects have the same property but each can contain different values.
Properties are actually a little simpler than variables because there are really only two types: regular properties and protected properties.
The property in the example above is a regular property. That means that it's accessible from anywhere as long as you have a reference to the object that it's a part of. e.g.f = new MyForm() f.Open() ? f.IsOpenProtected properties on the other hand are hidden to anything outside of the class itself. Protecting properties is a good way to hide the internal workings of your objects and to prevent them from being changed by some other object.
Making a protected property is easy. All you need to do is use the "protect" keyword. Let me change my example:Class MyForm of Form PROTECT isOpen this.isOpen = falseFunction OnOpen this.isOpen := true return Function OnClose this.isOpen := false return endclassIf you tried this code again:f = new MyForm() f.Open() ? f.IsOpenYou'd get an error: “Property is not accessible”
Protecting it makes it inaccessible to anything but the class itself. That means you could only use the IsOpen property within the class/endclass statements. The property also won't appear in the inspector.
There's one small problem with the above code - namely that the form designer doesn't stream out the protected keyword. If you were to design this form the PROTECT isOpen line would be removed next time you saved. Fortunately there's a nifty solution to that. Try the following code:f = new MyForm() f.Open() inspect(f)Class MyForm of Form Function OnOpen PROTECT isOpen this.isOpen = true return Function OnClose this.isOpen = false return endclassNotice that I'm able to protect a property any time I want. In fact, if you want you can dynamically protect a property. Run this code then click on the form to see a second inspector pop up. In the second inspector window the isOpen property is suddenly hidden! Very cool indeed!f = new MyForm() f.Open() inspect(f)Class MyForm of Form Function OnOpen this.isOpen = true return Function OnClose this.isOpen = false return Function onLeftMouseUp protect isOpen inspect(this) return endclassO.K., enough with the funny experimentation. Let's move on. ;-)
Properties can be created in any number of ways. dBASE uses a dynamic object model which means that objects can be extended after they are created. This means that new properties can be added to an object at any time. e.g.Using Properties
Here's an example of using a custom property for a form.f = new ConfirmForm() f.mdi = .f. f.Prompt.Text = "Are you really really really REALLY sure you want to do this????" f.ReadModal() if f.YesClicked ?"You Clicked YES! Formatting the hard drive...." endif CLASS confirmFORM OF FORM this.AutoCenter = .T. this.Text = "Please confirm" this.Left = 54.166 this.Top = 7.4697 this.Height = 7.5879 this.Width = 59.166 DEFINE TEXT PROMPT OF THIS; PROPERTY; Alignment 10,; Text "Are you really really really sure you want to do this????",; FontSize 12,; Left 1,; FontBold .F.,; Top 0.9404,; Height 4.7061,; Width 56 DEFINE PUSHBUTTON YESBUTTON OF THIS; PROPERTY; Text "&Yes",; Left 2,; FontBold .F.,; Top 6.1172,; OnClick CLASS::YESBUTTON_ONCLICK,; Group .T.,; Height 1.1172,; Width 11.833 DEFINE PUSHBUTTON NOBUTTON OF THIS; PROPERTY; Text "&No",; Left 46,; FontBold .F.,; Top 6.1172,; OnClick CLASS::NOBUTTON_ONCLICK,; Group .T.,; Height 1.1172,; Width 11.833 Function ReadModal * I'll add my custom property here. form.YesClicked = .f. return Super::ReadModal() Procedure YESBUTTON_OnClick form.YesClicked = .t. form.Close() return Procedure NOBUTTON_OnClick form.Close() return ENDCLASSIn this example I used the ReadModal method of my form to create a custom property called YesClicked. Notice that because it's a property, I can access it from any method in the class.
Normally, you can create these custom properties in the constructor part of the class but in the case of the form I use the Open or ReadModal methods so that the form designer will keep my code.
Custom properties are especially useful when you need to share information with different functions inside your class. e.g.Common GotchasClass MyForm of Form Function onOpen form.recordSaved = false return Function SaveButton_onClick form.rowset.save() form.recordSaved := true return Function canClose local lCanClose lCanClose = true if not form.recordSaved msgbox("Please save the record first!") lCanClose := false endif return ( lCanClose ) endclassYou could use a public for this. e.g.:Class MyForm of Form Function onOpen public recordSaved recordSaved = false return Function SaveButton_onClick form.rowset.save() recordSaved := true return Function canClose local lCanClose lCanClose = true if not recordSaved msgbox("Please save the record first!") lCanClose := false endif return ( lCanClose ) endclassBut what's wrong with this code? Nothing really. It will work if your application only has one form. What would happen if your users wanted an MDI (Multiple Document Interface) application? What if they wanted to be able to open 4 or 5 copies of the same form to view / edit? You would be in deep do-do. Each form would overwrite the others “recordSaved” value. You'd never be sure which form you were talking about!
The above code is a pretty simple example but this is what I meant by circumventing the object model.
In keeping with Peter Rorlick's dQUIZ idea, I'll show you some code examples. Try to guess what the problem is with each example before looking at the answer. Don't cheat! :)General Tips
Look at the following example and guess what the output will be:* Gotcha # 1 a() function a ?program() for j = 1 to 10 ?j b() next j return function b ?program() for j = 1 to 10 ?j next j returnAnswer #1
Look at this code and guess what the output will be: ( this one's easy )* Gotcha #2local object1, object2 object1 = New MyObject() object1.SetValue(10) object2 = New MyObject() object2.SetValue(20) ?object1.GetValue() ?object2.GetValue() Class MyObject public nValue Function SetValue(n) nValue = n return Function GetValue return ( nValue ) endclassAnswer #2
Copy & paste the following code. What do you think the results will be?local string, x string = "Dan Howard" ?"Original Value of STRING: " + string x = upper(string) ?"Value of STRING after calling upper function: " + string x = MyUpper(string) ?"Value of STRING after calling MY upper function: " + string Function MyUpper(cStr) cStr = upper(cStr) return (cStr)Answer #3
What do you think will happen in the following code?o = new MyObject() o.Display() class MyObject private hello hello = "hello" function Display ?hello return null endclassAnswer #4
Here are few guidelines I try to live by. They can help make your programming time easier so you'll spend less time in the debugger.
- Generally, never create publics or privates; use locals instead. And never use the parameters statement, use local parameters instead. (except if you need macros of course)
- If a sub function needs to access a variable, pass it as a parameter instead of making it private.
- Strive to create custom properties instead of variables when appropriate.
- Use custom properties of the _app object instead of publics. But with a good OOP approach, values having global scope and permanent lifetime are rarely needed - so strive to avoid them.
Well I hope I gave you enough to think about. The things to remember are to:
- Only use publics when dealing with things specific to the applications itself
- Use properties when dealing with objects
- Use variables when dealing with functions
Dan Howard has
been an independent software developer for almost 10 years using Clipper
for DOS and Visual dBASE for Windows. He was recently knighted a
dBVIPS member and he recently did receive his T-Shirt which he as worn
ever since so it smells funny now. He can be contacted at: firstname.lastname@example.org