CATScript Object-Oriented-Programming with CATScript

Object-oriented programming (OOP) is not commonly used in CATScript macros, or at least, there is not so much information and sample-code available on the web or in various books and documentation I looked at.

However, CATScript - similar to VBS - does support class declarations, so there is no excuse for an adventurous programmers to not use it.

OOP What is it good for?

Some of the benefits of using object-oriented programming in CATScript are:

  • Encapsulation and derivation, which makes the code more organized
  • Cleaner and more reusable code blocks and classes
  • Public functions that can be lay-outed quite similar to the CATIA API
  • Object-oriented programming requires more thought before development, but it is easier to extend and adopt later in the course of the development process.

In modern programming languages which support OOP like c++, classes can have a variety of features, including inheritance, polymorphism, encapsulation, abstraction, and more. In VBA or CATScript however, only a small sub-set of these features is available.

ChatGPT - Let’s give it a trial

Here is an interactive ChatGPT Q&A session to find out more about OOP in combination with VBA / CATScript.

Let’s write a basic class template showing all features available with OOP…


Here is a basic class template in VBA that demonstrates some of the features available in Object-Oriented Programming (OOP):

' Define the class name
Class MyClass

    ' Define the private variables
    Private var1 As Integer
    Private var2 As String

    ' Define the public properties
    Public Property Get Property1() As Integer
        Property1 = var1
    End Property

    Public Property Let Property1(value As Integer)
        var1 = value
    End Property

    Public Property Get Property2() As String
        Property2 = var2
    End Property

    Public Property Let Property2(value As String)
        var2 = value
    End Property

    ' Define the public methods
    Public Sub Method1()
        ' Do something
    End Sub

    Public Function Method2(parameter1 As Integer, parameter2 As String) As String
        ' Do something with the parameters
        Method2 = parameter2 & parameter1
    End Function

End Class

This class has two private variables (var1 and var2) and two public properties (Property1 and Property2) that can be used to access and modify these variables.

It also has two public methods (Method1 and Method2) that can be used to perform some actions.


Let’s extend the template with a Class_Intitialize and Class_Terminate event…


Sure, here is the updated class template that includes Class_Initialize and Class_Terminate events:

' Define the class name
Class MyClass

    ' Define the private variables
    Private var1 As Integer
    Private var2 As String

    ' Define the public properties
    Public Property Get Property1() As Integer
        Property1 = var1
    End Property

    Public Property Let Property1(value As Integer)
        var1 = value
    End Property

    Public Property Get Property2() As String
        Property2 = var2
    End Property

    Public Property Let Property2(value As String)
        var2 = value
    End Property

    ' Define the public methods
    Public Sub Method1()
        ' Do something
    End Sub

    Public Function Method2(parameter1 As Integer, parameter2 As String) As String
        ' Do something with the parameters
        Method2 = parameter2 & parameter1
    End Function

    ' Define the Class_Initialize event
    Private Sub Class_Initialize()
        ' Do something when the class is initialized
        var1 = 0
        var2 = ""
    End Sub

    ' Define the Class_Terminate event
    Private Sub Class_Terminate()
        ' Do something when the class is terminated
    End Sub

End Class

The Class_Initialize event is called automatically when an instance of the class is created, and can be used to initialize any properties or variables that need to be set when the object is created.

The Class_Terminate event is called automatically when the instance of the class is destroyed, and can be used to perform any cleanup tasks that need to be done before the object is destroyed, such as closing files or releasing resources.

These events can be used to ensure that the object is properly initialized and cleaned up, and can help prevent errors and memory leaks in your code.


Now lets’s add an class instantiation example at the end of the template code…


Sure, here’s an example of how you can instantiate the MyClass class in CATScipt:

Sub Example()
    ' Declare a variable of type MyClass
    Dim myObject As MyClass
    
    ' Instantiate the class
    Set myObject = New MyClass
    
    ' Use the properties and methods of the class
    myObject.Property1 = 10
    myObject.Property2 = "Hello, world!"
    
    myObject.Method1
    
    Dim result As String
    result = myObject.Method2(5, "Parameter: ")
    Debug.Print result
    
    ' Destroy the object when done
    Set myObject = Nothing
End Sub

In this example, we first declare a variable myObject of type MyClass. We then instantiate the class using the New keyword and assign the instance to the myObject variable using the Set keyword.

We can then use the properties and methods of the class as needed, such as setting Property1 and Property2, calling Method1 and passing arguments to Method2. Finally, we set myObject to Nothing to release the memory used by the object.

Note that the Class_Initialize event will be called automatically when the New keyword is used to create the instance, and the Class_Terminate event will be called automatically when the myObject variable is set to Nothing or goes out of scope.


Supported or not supported ?

Can I overload functions? in other words can I do this:

Public Sub Method1(ByVal str As String)
    ' Do something
End Sub
Public Sub Method1(ByVal i As Integer)
    ' Do something
End Sub


No, in VBA you cannot have two methods with the same name and parameter list, even if the data types of the parameters are different. This is because VBA/CATScript does not support method overloading, which is a feature found in many other programming languages that allows methods to have the same name but different parameter lists.

If you try to define two methods with the same name and parameter list, you will get a compile-time error. To avoid this error, you can either give the methods different names, or you can use optional parameters to simulate method overloading. For example:

Public Sub Method1(ByVal str As String, Optional ByVal i As Integer)
    If Not IsMissing(i) Then
        ' Do something with str and i
    Else
        ' Do something with str
    End If
End Sub

In this example, the i parameter is optional, so you can call the method with either one or two arguments. If you call the method with only one argument, the i parameter will be set to the default value of 0. If you call the method with two arguments, the i parameter will be set to the second argument.


Can I use variable arguments for a proc or a function?


Yes, in VBA you can use variable arguments in a procedure or function by using the ParamArray keyword. This allows you to pass an arbitrary number of arguments to the procedure or function.

Here is an example of a procedure that takes a variable number of arguments:

Sub Example(ParamArray args() As Variant)
    Dim i As Integer
    For i = LBound(args) To UBound(args)
        Debug.Print args(i)
    Next i
End Sub

In this example, the ParamArray keyword is used to declare a variable-length argument list named args. The LBound and UBound functions are used to loop through the array of arguments and print each one to the Immediate window.

You can call this procedure with any number of arguments, like this:

Example "Hello", "world", 123, True


Can i code something like Set myClass = New myCustomClass (“test”) in VBA?


Yes, you can use the “New” keyword to create a new instance of a custom class in VBA, just like in other object-oriented programming languages. Here’s an example:

' Define a custom class called "myCustomClass"
Class myCustomClass
    Private myString As String
    
    ' Constructor method that sets the value of "myString"
    Public Sub Class_Initialize()
        myString = ""
    End Sub
    
    ' Constructor method that takes a string argument and sets the value of "myString"
    Public Sub New(s As String)
        myString = s
    End Sub
    
    ' Public method that returns the value of "myString"
    Public Function GetString() As String
        GetString = myString
    End Function
End Class

' Create a new instance of "myCustomClass" and pass a string argument to the constructor
Dim myClass As New myCustomClass("test")

' Call the "GetString" method of the new instance and display the result in a message box
MsgBox myClass.GetString()

This code creates a new instance of the “myCustomClass” class, passing the string “test” as an argument to the constructor. It then calls the “GetString” method of the new instance to retrieve the value of the “myString” field and displays the result in a message box.


Syntax checking

Is this class syntactically correct?
This code contains 2 fundamental errors, I would like to know what happens here:

' Define the class name
Class CustomStrArray

    ' Define the private variables
    Private csa() As String

    ' Define the public properties
    Public Property Get Count() As String
        Count = UBound(csa)
    End Property

    ' Define the public methods
    Public Sub Add(ByVal str As String)
		ReDim (UBound(csa)+1)
		csa(UBound(csa)+1) = str
    End Sub

    ' Define the Class_Initialize event
    Private Sub Class_Initialize()
        ' Do something when the class is initialized
        ReDim csa(0)
    End Sub

    ' Define the Class_Terminate event
    Private Sub Class_Terminate()
        ReDim csa(0)
    End Sub

End Class


No, this class has a syntax error in the Add method. The ReDim statement is not using the correct syntax to preserve the existing values in the array. Here’s the corrected code:

Class CustomStrArray

    ' Define the private variables
    Private csa() As String

    ' Define the public properties
    Public Property Get Count() As Long
        Count = UBound(csa) + 1
    End Property

    ' Define the public methods
    Public Sub Add(ByVal str As String)
        ReDim Preserve csa(UBound(csa) + 1)
        csa(UBound(csa)) = str
    End Sub

    ' Define the Class_Initialize event
    Private Sub Class_Initialize()
        ' Do something when the class is initialized
        ReDim csa(0)
    End Sub

    ' Define the Class_Terminate event
    Private Sub Class_Terminate()
        ReDim csa(0)
    End Sub

End Class

Changes made to the original code:

  • In the Count property, the return type has been changed from String to Long.
  • In the Add method, the ReDim statement has been changed to ReDim Preserve to preserve the existing values in the array. The index used to assign the new value to the array has been changed to UBound(csa) instead of UBound(csa) + 1.
  • In the Count property, the return value has been changed to UBound(csa) + 1 to return the correct count of items in the array.

Give me a practical use case for the class.


Sure! Here’s an example use case for the CustomStrArray class:

Let’s say you’re building a program that needs to store a list of names. You could use the CustomStrArray class to create an array of strings that allows you to easily add and remove names, as well as get the count of names in the array.

Here’s some example code that uses the CustomStrArray class:

' Create a new instance of the CustomStrArray class
Dim names As New CustomStrArray

' Add some names to the array
names.Add "Alice"
names.Add "Bob"
names.Add "Charlie"

' Print the count of names in the array
Debug.Print "Number of names: " & names.Count

' Print each name in the array
Dim i As Long
For i = 0 To names.Count - 1
    Debug.Print names(i)
Next i

' Remove a name from the array
names.Remove "Bob"

' Print the count of names in the array again
Debug.Print "Number of names: " & names.Count

This program creates a new instance of the CustomStrArray class, adds some names to the array, prints the count of names in the array, prints each name in the array, removes a name from the array, and then prints the count of names in the array again.

The CustomStrArray class makes it easy to manage the list of names, without having to worry about the underlying details of the array implementation.


The Remove member function is not implemented, so let’s add this to correct the code…


Public Sub Remove(ByVal str As String)
	Dim i As Long
	For i = LBound(csa) To UBound(csa)
		If csa(i) = str Then
			' Shift elements after i one position to the left
			Dim j As Long
			For j = i To UBound(csa) - 1
				csa(j) = csa(j + 1)
			Next j
			' Shrink the array by one element
			ReDim Preserve csa(UBound(csa) - 1)
			Exit For
		End If
	Next i
End Sub

The Remove method loops through the elements of the array and removes the first occurrence of the specified string. It does this by shifting all the elements after the removed element one position to the left, and then shrinking the array by one element.

With this updated implementation, you can now use the Remove method to remove elements from the CustomStrArray class. For example:

' Create a new instance of the CustomStrArray class
Dim names As New CustomStrArray

' Add some names to the array
names.Add "Alice"
names.Add "Bob"
names.Add "Charlie"

' Remove a name from the array
names.Remove "Bob"

' Print the count of names in the array
Debug.Print "Number of names: " & names.Count

' Print each name in the array
Dim i As Long
For i = 0 To names.Count - 1
    Debug.Print names(i)
Next i

Let’s modify the code, so that the index of the array start by 1 instead of 0 ?


Sure, here’s the modified class code with the index starting at 1:

Class CustomStrArray

    ' Define the private variables
    Private csa() As String

    ' Define the public properties
    Public Property Get Count() As Long
        Count = UBound(csa)
    End Property

    ' Define the public methods
    Public Sub Add(ByVal str As String)
        ReDim Preserve csa(UBound(csa) + 1)
        csa(UBound(csa)) = str
    End Sub

    Public Sub Remove(ByVal index As Long)
        If index < 1 Or index > UBound(csa) Then
            Err.Raise 9, , "Index out of range"
            Exit Sub
        End If
        Dim i As Long
        For i = index To UBound(csa) - 1
            csa(i) = csa(i + 1)
        Next i
        ReDim Preserve csa(UBound(csa) - 1)
    End Sub

    Public Property Get Item(ByVal index As Long) As String
        If index < 1 Or index > UBound(csa) Then
            Err.Raise 9, , "Index out of range"
        Else
            Item = csa(index - 1)
        End If
    End Property

    Public Property Let Item(ByVal index As Long, ByVal value As String)
        If index < 1 Or index > UBound(csa) Then
            Err.Raise 9, , "Index out of range"
        Else
            csa(index - 1) = value
        End If
    End Property

    ' Define the Class_Initialize event
    Private Sub Class_Initialize()
        ' Do something when the class is initialized
        ReDim csa(0)
    End Sub

    ' Define the Class_Terminate event
    Private Sub Class_Terminate()
        ReDim csa(0)
    End Sub

End Class

Note that the Item property and the Remove method have been modified to start indexing at 1.


Great - the example code would look like?


Here’s an example of how to use the modified class with the index starting at 1:

' Create an instance of the CustomStrArray class
Dim arr As New CustomStrArray

' Add some elements to the array
arr.Add "apple"
arr.Add "banana"
arr.Add "cherry"

' Get the number of elements in the array
MsgBox "Count: " & arr.Count ' Output: Count: 3

' Get the second element of the array
MsgBox "Element 2: " & arr.Item(2) ' Output: Element 2: banana

' Change the value of the third element
arr.Item(3) = "date"

' Remove the first element
arr.Remove 1

' Get the new number of elements in the array
MsgBox "Count: " & arr.Count ' Output: Count: 2

' Get the first element of the array
MsgBox "Element 1: " & arr.Item(1) ' Output: Element 1: banana

Note that the Item property and the Remove method are now called with indices starting at 1.


The final CustomStrArray Class


Finally print everything again with the following changes:

  • add the example at the end of the class,
  • the remove member function should take a string as argument


Here’s the modified class code with the changes as requested:

Class CustomStrArray

    ' Define the private variables
    Private csa() As String

    ' Define the public properties
    Public Property Get Count() As Long
        Count = UBound(csa)
    End Property

    ' Define the public methods
    Public Sub Add(ByVal str As String)
        ReDim Preserve csa(UBound(csa) + 1)
        csa(UBound(csa)) = str
    End Sub
    
    Public Sub Remove(ByVal str As String)
        Dim i As Long
        For i = 1 To UBound(csa)
            If csa(i) = str Then
                If i < UBound(csa) Then
                    csa(i) = csa(UBound(csa))
                End If
                ReDim Preserve csa(UBound(csa) - 1)
                Exit For
            End If
        Next i
    End Sub

    ' Define the Item property
    Public Property Get Item(ByVal index As Long) As String
        Item = csa(index)
    End Property

    Public Property Let Item(ByVal index As Long, ByVal value As String)
        csa(index) = value
    End Property

    ' Define the Class_Initialize event
    Private Sub Class_Initialize()
        ' Do something when the class is initialized
        ReDim csa(0)
    End Sub

    ' Define the Class_Terminate event
    Private Sub Class_Terminate()
        ReDim csa(0)
    End Sub

End Class

' Example usage of the CustomStrArray class
Dim arr As New CustomStrArray

arr.Add "apple"
arr.Add "banana"
arr.Add "cherry"

MsgBox "Count: " & arr.Count ' Output: Count: 3

arr.Item(2) = "orange"

MsgBox "Element 2: " & arr.Item(2) ' Output: Element 2: orange

arr.Remove "banana"

MsgBox "Count: " & arr.Count ' Output: Count: 2

MsgBox "Element 1: " & arr.Item(1) ' Output: Element 1: apple

Note that the Remove method now takes a string argument instead of an integer index and an example usage of the class has been added at the end of the code.


Possible to extend the class?


Is it possible to extend the class so that the class can either handle strings or objects at the same time, please adopt this change and update the code with the class and an example section.


Certainly! Here’s an updated version of the class that can handle both strings and objects:

Class CustomArray

    ' Define the private variables
    Private arr() As Variant

    ' Define the public properties
    Public Property Get Count() As Long
        Count = UBound(arr) - LBound(arr) + 1
    End Property

    ' Define the public methods
    Public Sub Add(ByVal element As Variant)
        ReDim Preserve arr(LBound(arr) To UBound(arr) + 1)
        arr(UBound(arr)) = element
    End Sub

    Public Sub Remove(ByVal element As Variant)
        Dim i As Long
        For i = LBound(arr) To UBound(arr)
            If arr(i) = element Then
                If i < UBound(arr) Then
                    arr(i) = arr(UBound(arr))
                End If
                ReDim Preserve arr(LBound(arr) To UBound(arr) - 1)
                Exit For
            End If
        Next i
    End Sub

    ' Define the Class_Initialize event
    Private Sub Class_Initialize()
        ' Do something when the class is initialized
        ReDim arr(1 To 0)
    End Sub

    ' Define the Class_Terminate event
    Private Sub Class_Terminate()
        ReDim arr(1 To 0)
    End Sub

End Class

And here’s an example usage:

Sub Example()
    Dim myArray As New CustomArray
    myArray.Add "Hello"
    myArray.Add 123
    Dim obj As New MyClass
    myArray.Add obj
    Debug.Print "Count: " & myArray.Count ' Prints "Count: 3"
    myArray.Remove "Hello"
    Debug.Print "Count: " & myArray.Count ' Prints "Count: 2"
End Sub

Class MyClass
    ' Define the properties and methods of MyClass
End Class

For the add member function is there no Set statement required in case the variant is an Object?


If you want to add an object to the array, you can simply assign the object to the array element. In this case, you don’t need to use the Set keyword.

For example, consider the following modified Add method of the CustomVariantArray class:

Public Sub Add(ByVal var As Variant)
    If IsObject(var) Then
        ReDim Preserve cva(LBound(cva) To UBound(cva) + 1)
        Set cva(UBound(cva)) = var
    Else
        ReDim Preserve cva(LBound(cva) To UBound(cva) + 1)
        cva(UBound(cva)) = var
    End If
End Sub

Here, the IsObject function is used to check whether the variant is an object or not. If the variant is an object, we use the Set keyword to assign the object to the array element. If the variant is not an object, we simply assign the value to the array element without using the Set keyword.

Note that when assigning an object to a variable or an array element, you must use the Set keyword to set the reference to the object.


The 1st sentence of your answer is a bit unlogic, the rest of the explanation seems to be ok.


I apologize for any confusion caused by my previous answer. To clarify, when adding an object to an array, you do not need to use the Set keyword to assign the object to the array element. Instead, you can simply assign the object to the array element.

In VBA, the Set keyword is used to assign an object reference to an object variable, not to an array element. When you assign an object to an array element, VBA automatically assigns a reference to the object, so you do not need to use the Set keyword in this case.

Thank you for bringing this to my attention, and I hope this clears up any confusion.


I am going to test this out, looks like that VBA can handle the type declaration Set cva(UBound(cva)) = var and cva(UBound(cva)) = var in case var is either a string or an object automatically.
If so it would be save to use for example TypeName(var(2))


Yes, that’s correct. In VBA, you can use both Set cva(UBound(cva)) = var and cva(UBound(cva)) = var to assign objects to an array element. And you can use TypeName(var) to get the type name of the object in the array. However, if the array element contains a string, then you don’t need to use Set to assign the value, you can simply use cva(UBound(cva)) = var as you would for a non-object variable.

The drawback with ChatGPT AI as far as I see: as long as you ask the right questions, you get pretty good answers. However, to ask the right questions you need to know the programing language already.

When digging deeper into a detail, sometimes the answer is not consistent all the time. See last questions about the need to use the set keyword for an array or not.