Saturday, December 26, 2009

Properties in BAS modules

While this fact is undocumented in Visual Basic manuals, you can create Property procedures in standard BAS modules as well. This capability makes a few interesting techniques possible. You can use a pair of Property procedures to encapsulate a global variable and arbitrate all accesses to it. Let’s say that you have a global Percent variable:
‘ In a standard BAS module
Public Percent As Integer
For really robust code, you want to be sure that all values assigned to it are in the proper 0 through 100 range, but you don’t want to test all the assignment statements in your code. The solution is easy, as you’ll see on the following page.
Dim m_Percent As Integer

Property Get Percent() As Integer
Percent = m_Percent
End Property
Property Let Percent(newValue As Integer)
If newValue < 0 Or newValue > 100 Then Err.Raise 5
m_Percent = newValue
End Property
Other interesting variations of this technique are read-only and write-once/read-many global variables. You can also use this technique to work around the inability of Visual Basic to declare string constants that contain Chr$ functions and concatenation operators:
‘ You can’t do this with a CONST directive.
Property Get DoubleCrLf() As String
DoubleCrLf = vbCrLf & vbCrLf
End Property
Finally, you can use Property procedures in BAS modules to trace what happens to the global variables in your code. Let’s say that your code incorrectly assigns a wrong value to a global variable, and you want to understand when this happens. Just replace the variable with a pair of Property procedures, and add Debug.Print statements as required (or print values to a file, if you want). When you have fixed all the problems, delete the procedures and restore the original global variable. The best thing about all this is that you won’t need to edit a single line of code in the rest of your application.
The CallByName Function
Visual Basic 6 includes a welcome addition to the VBA language, in the form of the CallByName function. This keyword lets you reference an object’s method or property by passing its name in an argument. Its syntax is as follows:
result = CallByName(object, procname, calltype [,arguments..])
where procname is the name of the property or method, and calltype is one of the following constants: 1-vbMethod, 2-vbGet, 4-vbLet, 8-vbSet. You must pass any argument the method is expecting, and you should avoid retrieving a return value if you’re invoking a Sub method or a Property Let/Get procedure. Here are a few examples:
Dim pers As New CPerson
‘ Assign a property.
CallByName pers, "FirstName", vbLet, "Robert"
‘ Read it back.
Print "Name is " & CallByName(pers, "FirstName", vbGet)
‘ Invoke a function method with one argument.
width = CallByName(Form1, "TextWidth", vbMethod, "ABC")
Here are a couple of noteworthy bits of information about this function, both of which affect its usefulness:
• While adding a lot of flexibility when dealing with an object, neither the CallByName function nor the VBA language as a whole is able to retrieve the list of the properties and methods exposed by an object. So in this sense the CallByName function is only a half-solution to the problem because you have to build the property names yourself. If you know these names in advance, you might invoke your properties and methods directly, using the familiar dot syntax.
• The CallByName function invokes the object’s member using a late binding mechanism (see "The Binding Mechanism" later in this chapter), which is considerably slower than regular access through the dot syntax.
As a general rule, you should never use the CallByName function when you can reach the same result using the regular dot syntax. There are times, however, when this function permits you to write very concise and highly parameterized code. One interesting application is quickly setting a large number of properties for controls on a form. This might be useful when you give your users the ability to customize a form and you then need to restore the last configuration in the Form_Load event. I have prepared a couple of reusable procedures that do the job:
‘ Returns an array of "Name=Values" strings
‘ Supports only nonobject properties, without indices
Function GetProperties(obj As Object, ParamArray props() As Variant) _
As String()
Dim i As Integer, result() As String
On Error Resume Next
‘ Prepare the result array.
ReDim result(LBound(props) To UBound(props)) As String
‘ Retrieve all properties in turn.
For i = LBound(props) To UBound(props)
result(i) = vbNullChar
‘ If the call fails, this item is skipped.
result(i) = props(i) & "=" & CallByName(obj, props(i), vbGet)
Next
‘ Filter out invalid lines.
GetProperties = Filter(result(), vbNullChar, False)
End Function

‘ Assign a group of properties in one operation.
‘ Expects an array in the format returned by GetProperties
(continued) Sub SetProperties(obj As Object, props() As String)
Dim i As Integer, temp() As String
For i = LBound(props) To UBound(props)
‘ Get the Name-Value components.
temp() = Split(props(i), "=")
‘ Assign the property.
CallByName obj, temp(0), vbLet, temp(1)
Next
End Sub
When you’re using GetProperties, you have to provide a list of the properties you’re interested in, but you don’t need a list when you restore the properties with a call to SetProperties:
Dim saveprops() As String
saveprops() = GetProperties(txtEditor, "Text", "ForeColor", "BackColor")
...
SetProperties txtEditor, saveprops()

No comments:

Post a Comment