Advanced OOP features
This section covers more advanced features of classes. You will not need these features often, but they are definitely useful.
Assigning a value to a method
Sometimes it is helpful, from a syntax standpoint, to have a method that gets a value using an assignment operator.
Rather than doing this:
You would write:
order.Quantity = 2
which looks very much like a property. You can get the syntax behavior of a property using a method by using the Assigns keyword in the parameter list.
The declaration looks like this:
Sub Quantity(Assigns amount As Integer)
You can also have more than one parameter, something that you can not do with a simple property:
Sub Quantity(productName As String, Assigns amount As Integer)
This command would be used like this:
order.Quantity("Book") = 2
When you use this technique, it will look like you are setting a property, even though you are really dealing with a method. This can be handy when designing classes and can make your code easier to read.
You can easily overload methods on a class, but how do you overload an operator on a class? For example, what if you have two instances of a SalesOrder class and want to tell if one is greater than the other?
You do this by implementing one of the Operator overload methods (Figure 5.10) on the SalesOrder class, in this case Operator_Compare.
Sub Operator_Compare(rightOrder As SalesOrder) If Self.TotalAmount > rightOrder.TotalAmount Then Return 1 ElseIf Self.TotalAmount < rightOrder.TotalAmount Then Return -1 Else Return 0 End If End Sub
These are the operator overload methods:
=, <, >, <=, >=
For more information about operator overloading for custom classes, refer to Operator Overloading.
Dot notation is used to access methods and properties of a class instance. If you try to access a method or property that does not exist, you get a compilation error.
Operator Lookup is a special class method that is called when anything following the "." that is not an actual method or property of the class is used. One use for this feature is to look up a value that may not be known until runtime. Suppose you are creating a Preferences class that will be used to save your application preferences. Normally, you would have to create a property for each preference you want to save. If you find you have a new preference value, you 'll need to go back to the class and add the property before you can use it.
But you could use operator lookup instead.
With operator lookup, you use the method to check what was typed after the "." and have that be the name of the preference. The value that is assigned can then be stored (perhaps using a Dictionary).
Your code to save a value might look like this:
Sub Operator_Lookup(name As String, Assigns value As String) If mPreferenceDict = Nil Then mPreferenceDict = New Dictionary End If mPreferenceDict.Value(name) = value End Sub
And the code to get a value might look like this:
Function Operator_Lookup(name As String) As String If mPreferenceDict = Nil Then Return "" If mPreferenceDict.HasKey(name) Then Return mPreferenceDict.Value(name) End If End Sub
With this in place, you can now write code to save preference values even though you have never defined specific properties or methods for the preference. In the App class, add a property called Prefs as Preferences. In the App.Opening event, initialize this property:
Prefs = New Preferences
Now, anywhere in your project you can write code like this to store a preference value:
App.Prefs.UserName = "Mary Roberts" App.Prefs.UserEmail = "email@example.com"
And code like this to get a preference value:
Var userName As String userName = App.Prefs.UserName // Not an actual property, but calls Operator_Lookup
Attributes are compile-time properties. They can be added to project items and code items such as method and properties. An attribute consists of its Name and its Value. The Name is required, but the value is optional.
Attributes are added using the advanced tab of the Inspector. Attributes can be added to classes, modules, windows, containers, interfaces and toolbars. A subclass inherits attributes from its super class. These inherited values can be overridden by the subclass by redefining them.
Creating an attribute
You create an attribute using the Attribute Editor in the advanced tab of the Inspector for the item. Use the “+” or “-” buttons to add or remove attributes. Specify the Name and Value for the attribute in the list.
There are two ways to specify the value. If it is a literal value, such as “ID”, then the value must be enclosed in quotes. If it is a constant, you can just use the name of the constant, for example kID. If you forget the quotes for a literal value, you will get a compiler error if the constant is not found.
Accessing an attribute
Attributes are accessed in your code using Introspection. The AttributeInfo class is used to fetch the Name-Value attribute pairs for a particular object. This code gets the attribute values of the default window and displays them in a List Box:
Var myAttributes() As Introspection.AttributeInfo = Introspection.GetType(Window1).GetAttributes For i As Integer = 0 To myAttributes.LastIndex ListBox1.AddRow(myAttributes(i).Name) If myAttributes(i).Value.IsNull Then ListBox1.CellTextAt(ListBox1.LastIndex, 1) = "No Value" Else ListBox1.CellTextAt(ListBox1.LastIndex, 1) = myAttributes(i).Value.ToString End If Next
There are several reserved attributes that perform special actions when used on a project item (such as a class or module) or a method, property (or constant, etc.) that you can add to the item. These attributes may be useful on frameworks you create for use by other developers.
Deprecated: The Deprecated attribute allows you to indicate that an item “has a replacement” that should instead be used. Set the attribute value to the name of the class/method/property that should be used instead (be sure to enclose the name with quotes). A deprecated item appears in the Errors Panel when you use Analyze Project.
Hidden: The Hidden attribute hides the specified item from introspection, the debugger and auto-complete. You do not need to specify a value.
HideFromLibrary: The project item will not be shown in the Library's Project Controls section. You do not need to specify a value.
StructureAlignment: This attribute adjusts how structures align on byte boundaries.
DefaultEvent: When creating your own control classes, you can add this property to determine the event that is selected by default in the Add Event Handler window. Set the attribute value to the name of an event definition on the class (be sure to enclose the name with quotes).
An extremely powerful way of creating generic, reusable code is to take advantage of the ability to convert an instance of a class to an instance of its subclass. The idea is best illustrated by an example. Since the objects you create are subclasses of base classes, you can always test to see whether an object is a member of a specific subclass. The IsA operator does this.
With the IsA operator, you test whether an object is of a specific subclass and, if it is, cast it as that type to do something specific with it. You cast an object by using the classname as a function that operates on the instance.
When you cast an instance, compile-time error-checking cannot guarantee that you are casting to a legal object type. The instance that you are casting has to be of the type that you specify. Casting just tells your code to treat the object as a instance of the class to which it is cast. It doesn 't convert it from one class to another. If you cast incorrectly, you will get an IllegalCastException at runtime.
Here is an example that uses a For loop to cycle through all the controls in a window. It checks each control to see if it is a Label and if it is, it casts the control and displays its text:
Var labelText As String // Loop through controls in window For Each aControl As DesktpUIControl in Self.Controls If aControl IsA DesktopLabel Then // Cast the control to a Label // and gets its Text property labelText = DesktopLabel(aControl).Text MessageBox(labelText) End If Next
As you can see, the code does not refer to any specific windows, Labels or anything else. Therefore, you can write this routine once and use it in any window in any project.
Type casting rules
Generally speaking, type casts take a value and reinterpret it as some other type. The exact rules differ based off of the type of the value being cast. Below are some examples:
If the value being cast is an object, the value can be cast to different classes or class interface types. This will get type checked at runtime and raise a TypeMismatchException is it's an invalid cast.
If the value being cast is an Integer type, it can be cast to a Ptr, Color, or Enumeration. The restriction is that the size of the destination type be the same as the Integer. That is to say, a UInt16 can't be cast to a UInt8 or a Ptr.
If the value being cast is an Enumeration type, it can be cast to Integers of the same size as its underlying type. It is worth noting is that unless explicitly specified, the underlying type of an Enumeration is Integer.
If the value being cast is a Color, it can be cast to Int32 or UInt32.
If the value being cast is a Ptr, it can be cast to Integer, UInteger, Int32 (32-bit), UInt32 (32-bit), Int64 (64-bit), or UInt64 (64-bit).
Everything else gives a compile time error and can't be cast.
A Static variable is a global variable that has the scope of a local variable.
It is declared in a method and, unlike with a normal local variable, the value is retained the next time the method in which it is declared is called. This value is retained for the method in all instances of the class, whether they already exist or are created later. You declare a Static variable by using the Static keyword in place of Var:
Static myValue As Integer
The most common use of a Static variable is when you need set a value using a method that might take a noticeable time to execute. By using a Static variable, you can ensure the initialization is only done once, but the value is available for re-use each time the method containing it is called.
Sorting arrays of classes
It is common to have arrays of classes that need to be sorted in some manner. For example, you may have a Customer class that has a LastName property and you'd like to use that to sort an array of Customer classes. The standard array Sort method can only sort simple types (Text, Integer, etc.) so it cannot be used to sort a class. Fortunately there are a couple alternatives you can use.
One way to do this is to use the Arrays.SortWith method and an additional array. What you do is create an all-new array of the simple type and copy the property values you want to sort to it. Then you use SortWith to sort the array with your class array. To take the Customer class as an example:
// customers() is a populated array of Customers, which have a LastName String property // First, copy the LastName property to a new array Var lastNames() As String For i As Integer = 0 To customers.LastIndex lastNames.Add(customers(i).LastName) Next // Now sort this new array with the customers array lastNames.SortWith(customers) // You no longer need the lastNames() array
The above technique works fine and is relatively easy to understand, but it does have the wasted step of copying the property value to its own array. So another alternative is to create your own comparison method and then tell the array Sort method to use your custom comparison method. First, you'll need to create your own comparison method.
Function CustomerLastNameCompare(c1 As Customer, c2 As Customer) As Integer // This assumes the array is populated with non-Nil Customers If c1.LastName > c2.LastName Then Return 1 If c1.LastName < c2.LastName Then Return -1 Return 0 End Function
Now you can call the Sort method, but tell it to delegate sorting to your CustomerLastNameCompare method:
// customers() is a populated array of Customers, which have a LastName Text property customers.Sort(AddressOf CustomerLastNameCompare)
Now the Customer array is sorted without having the overhead of creating a separate array.