How to use tools in Excel VBA

I am trying to implement some forms for an engineering project and abstract it for some common functions in order to have a generic program.

What I'm trying to do is have a cShape interface and have cRectangle and cCircle implement cShape

My code is below:

cShape interface

 Option Explicit Public Function getArea() End Function Public Function getInertiaX() End Function Public Function getInertiaY() End Function Public Function toString() End Function 

cRectangle class

 Option Explicit Implements cShape Public myLength As Double ''going to treat length as d Public myWidth As Double ''going to treat width as b Public Function getArea() getArea = myLength * myWidth End Function Public Function getInertiaX() getInertiaX = (myWidth) * (myLength ^ 3) End Function Public Function getInertiaY() getInertiaY = (myLength) * (myWidth ^ 3) End Function Public Function toString() toString = "This is a " & myWidth & " by " & myLength & " rectangle." End Function 

cCircle class

 Option Explicit Implements cShape Public myRadius As Double Public Function getDiameter() getDiameter = 2 * myRadius End Function Public Function getArea() getArea = Application.WorksheetFunction.Pi() * (myRadius ^ 2) End Function ''Inertia around the X axis Public Function getInertiaX() getInertiaX = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function ''Inertia around the Y axis ''Ix = Iy in a circle, technically should use same function Public Function getInertiaY() getInertiaY = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function Public Function toString() toString = "This is a radius " & myRadius & " circle." End Function 

The problem is that whenever I run my test cases, the following error occurs:

Compilation Error:

The object module must implement '~' for the interface '~'

+49
vba excel-vba interface excel
Oct. 15 '13 at 4:01
source share
5 answers

This is an esoteric concept of OOP, and you need to do a little more and understand, use a custom collection of shapes.

You can first view this answer to get an overview of the classes and interfaces in VBA.




Follow the instructions below.

First open Notepad and copy paste the code below

 VERSION 1.0 CLASS BEGIN MultiUse = -1 END Attribute VB_Name = "ShapesCollection" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Option Explicit Dim myCustomCollection As Collection Private Sub Class_Initialize() Set myCustomCollection = New Collection End Sub Public Sub Class_Terminate() Set myCustomCollection = Nothing End Sub Public Sub Add(ByVal Item As Object) myCustomCollection.Add Item End Sub Public Sub AddShapes(ParamArray arr() As Variant) Dim v As Variant For Each v In arr myCustomCollection.Add v Next End Sub Public Sub Remove(index As Variant) myCustomCollection.Remove (index) End Sub Public Property Get Item(index As Long) As cShape Set Item = myCustomCollection.Item(index) End Property Public Property Get Count() As Long Count = myCustomCollection.Count End Property Public Property Get NewEnum() As IUnknown Attribute NewEnum.VB_UserMemId = -4 Attribute NewEnum.VB_MemberFlags = "40" Set NewEnum = myCustomCollection.[_NewEnum] End Property 

Save the file as ShapesCollection.cls on your desktop.

Make sure you save it with *.cls , not ShapesCollection.cls.txt

Now open the Excel file, go to VBE ALT + F11 and right-click in Project Explorer . Select Import File from the drop-down menu and browse to the file.

enter image description here

Note. First you need to save the code in a .cls file, and then import it, because VBEditor does not allow you to use attributes. Attributes allow you to specify a default member in an iteration and use for each loop in custom collection classes

Read more: 1 , 2 , 3 , 4

Now insert 3 classes of modules. Rename accordingly and copy-paste code

cShape , this is your interface

 Public Function GetArea() As Double End Function Public Function GetInertiaX() As Double End Function Public Function GetInertiaY() As Double End Function Public Function ToString() As String End Function 

cCircle

 Option Explicit Implements cShape Public Radius As Double Public Function GetDiameter() As Double GetDiameter = 2 * Radius End Function Public Function GetArea() As Double GetArea = Application.WorksheetFunction.Pi() * (Radius ^ 2) End Function ''Inertia around the X axis Public Function GetInertiaX() As Double GetInertiaX = Application.WorksheetFunction.Pi() / 4 * (Radius ^ 4) End Function ''Inertia around the Y axis ''Ix = Iy in a circle, technically should use same function Public Function GetInertiaY() As Double GetInertiaY = Application.WorksheetFunction.Pi() / 4 * (Radius ^ 4) End Function Public Function ToString() As String ToString = "This is a radius " & Radius & " circle." End Function 'interface functions Private Function cShape_getArea() As Double cShape_getArea = GetArea End Function Private Function cShape_getInertiaX() As Double cShape_getInertiaX = GetInertiaX End Function Private Function cShape_getInertiaY() As Double cShape_getInertiaY = GetInertiaY End Function Private Function cShape_toString() As String cShape_toString = ToString End Function 

CRectangle

 Option Explicit Implements cShape Public Length As Double ''going to treat length as d Public Width As Double ''going to treat width as b Public Function GetArea() As Double GetArea = Length * Width End Function Public Function GetInertiaX() As Double GetInertiaX = (Width) * (Length ^ 3) End Function Public Function GetInertiaY() As Double GetInertiaY = (Length) * (Width ^ 3) End Function Public Function ToString() As String ToString = "This is a " & Width & " by " & Length & " rectangle." End Function ' interface properties Private Function cShape_getArea() As Double cShape_getArea = GetArea End Function Private Function cShape_getInertiaX() As Double cShape_getInertiaX = GetInertiaX End Function Private Function cShape_getInertiaY() As Double cShape_getInertiaY = GetInertiaY End Function Private Function cShape_toString() As String cShape_toString = ToString End Function 

Now you need to Insert standard Module and copy-paste the code below

Module1

 Option Explicit Sub Main() Dim shapes As ShapesCollection Set shapes = New ShapesCollection AddShapesTo shapes Dim iShape As cShape For Each iShape In shapes 'If TypeOf iShape Is cCircle Then Debug.Print iShape.ToString, "Area: " & iShape.GetArea, "InertiaX: " & iShape.GetInertiaX, "InertiaY:" & iShape.GetInertiaY 'End If Next End Sub Private Sub AddShapesTo(ByRef shapes As ShapesCollection) Dim c1 As New cCircle c1.Radius = 10.5 Dim c2 As New cCircle c2.Radius = 78.265 Dim r1 As New cRectangle r1.Length = 80.87 r1.Width = 20.6 Dim r2 As New cRectangle r2.Length = 12.14 r2.Width = 40.74 shapes.AddShapes c1, c2, r1, r2 End Sub 

Launch Main Sub and check the results in Immediate Window CTRL + G

enter image description here




Comments and explanations:

In your module of the ShapesCollection class, ShapesCollection are two subsets for adding items to the collection.

The first method, Public Sub Add(ByVal Item As Object) simply takes an instance of the class and adds it to the collection. You can use it in your Module1 like this

 Dim c1 As New cCircle shapes.Add c1 

Public Sub AddShapes(ParamArray arr() As Variant) allows you to add multiple objects at the same time, separating them with a comma , in the same way as AddShapes() Sub.

This is a good design than adding each object separately, but it depends on what you are going to go to.

Notice how I commented on some code in the loop

 Dim iShape As cShape For Each iShape In shapes 'If TypeOf iShape Is cCircle Then Debug.Print iShape.ToString, "Area: " & iShape.GetArea, "InertiaX: " & iShape.GetInertiaX, "InertiaY:" & iShape.GetInertiaY 'End If Next 

If you remove comments from the 'If and 'End If lines, you can only print cCircle objects. This would be very useful if you could use delegates in VBA, but you cannot, so I showed you a different way to print only one type of object. Obviously, you can change the If statement to suit your needs or simply print all the objects. Again, you decide how you are going to process your data :)

+69
Oct 15 '13 at 11:05
source share

There are two undocumented additions about VBA and instructions "Implemented".

  • VBA does not support the undescore character '_' in the method name of the inherited interface of a derived class. FE it will not compile code with a method such as cShape.get_area (tested in Excel 2007): VBA will throw a compilation error above for any derived class.

  • If the derived class does not implement its own method, named as in the interface, VBA successfully compiles the code, but this method will not be available through a variable of the type of the derived class.

+10
Oct 28 '14 at 9:26
source share

Here are a few theoretical and practical contributions to the answers to them, if people come here who are wondering what tools / interfaces are.

As you know, VBA does not support inheritance, so we can almost blindly use interfaces to implement common properties / behavior in different classes. However, I find it helpful to describe what the conceptual difference between the two is in order to understand why this matters later.

  • Inheritance: defines the is-a relationship (a square is a shape);
  • Interfaces: Define a required relationship (a typical example is the drawable interface, which requires the receiver to implement the draw method). This means that classes originating from different root classes can implement normal behavior.

Inheritance means that the base class (some physical or conceptual archetype) is extended, while interfaces implement a set of properties / methods that define a specific behavior.
Thus, we can say that Shape is a base class from which all other forms are inherited, which can implement the drawable interface to make all forms accessible. This interface will be a contract that ensures that each Shape has a draw method that determines how / where the figure should be drawn: a circle may or may not be made other than a square.

IDrawable class:

 'IDrawable interface, defining what methods drawable objects have access to Public Function draw() End Function 

Since VBA does not support inheritance, we are automatically forced to choose the IShape interface for creating the interface, which ensures that certain properties / behavior will be implemented using common shapes (square, circle, etc.), instead of creating an abstract base layer of the form from which we can spread.

IShape class:

 'Get the area of a shape Public Function getArea() As Double End Function 

The part in which we get into trouble is when we want to make each shape fit.
Unfortunately, since IShape is an interface, not a base class in VBA, we cannot implement the drawable interface in a base class. It seems that VBA does not allow us to use one interface for another; after checking this, the compiler does not seem to provide the desired behavior. In other words, we cannot implement IDrawable inside IShape and expect IShape instances to be forced to implement IDrawable methods because of this.
We are forced to implement this interface for every generic form class that implements the IShape interface, and, fortunately, VBA allows you to implement several interfaces.

cSquare class:

 Option Explicit Implements iShape Implements IDrawable Private pWidth As Double Private pHeight As Double Private pPositionX As Double Private pPositionY As Double Public Function iShape_getArea() As Double getArea = pWidth * pHeight End Function Public Function IDrawable_draw() debug.print "Draw square method" End Function 'Getters and setters 

The next part that follows now is where the typical use / benefits of an interface come into play.

Let our code begin by writing a factory that returns a new square. (This is just a workaround for our inability to send arguments directly to the constructor):

mFactory module:

 Public Function createSquare(width, height, x, y) As cSquare Dim square As New cSquare square.width = width square.height = height square.positionX = x square.positionY = y Set createSquare = square End Function 

Our main code will use factory to create a new square:

 Dim square As cSquare Set square = mFactory.createSquare(5, 5, 0, 0) 

When you look at the methods that you have at your disposal, you will notice that logically you get access to all the methods defined in the cSquare class:

enter image description here

We will see later why this is relevant.

Now you should be wondering what happens if you really want to create a collection of available objects. Your application may contain objects that are not shapes, but which are still available. Theoretically, nothing prevents you from having an IComputer interface that you can draw (maybe some kind of clipart or something else).
The reason you might need a collection of available objects is because you can display them in a loop at a certain point in the application's life cycle.

In this case, I will write a decorator class that wraps the collection (we will see why). class collDrawables:

 Option Explicit Private pSize As Integer Private pDrawables As Collection 'constructor Public Sub class_initialize() Set pDrawables = New Collection End Sub 'Adds a drawable to the collection Public Sub add(cDrawable As IDrawable) pDrawables.add cDrawable 'Increase collection size pSize = pSize + 1 End Sub 

The decorator allows you to add some convenient methods that cannot be provided in vba collections, but the actual point here is that the collection will only accept objects that can be rendered (implement the IDrawable interface). If we try to add an object that is not valid, a type mismatch will be selected (only valid objects are allowed!).

Thus, we could program the cycle of creating objects with the ability to draw. Allow an unattractive object in the collection will result in an error. The rendering outline might look like this:

Explicit option

 Public Sub app() Dim obj As IDrawable Dim square_1 As IDrawable Dim square_2 As IDrawable Dim computer As IDrawable Dim person as cPerson 'Not drawable(!) Dim collRender As New collDrawables Set square_1 = mFactory.createSquare(5, 5, 0, 0) Set square_2 = mFactory.createSquare(10, 5, 0, 0) Set computer = mFactory.createComputer(20, 20) collRender.add square_1 collRender.add square_2 collRender.add computer 'This is the loop, we are sure that all objects are drawable! For Each obj In collRender.getDrawables obj.draw Next obj End Sub 

Note that the above code adds a lot of transparency: we declared the objects as IDrawable, which makes it transparent, that the loop will never fail, since the draw method is available for all objects in the collection.
If we try to add Person to the collection, this will cause a type mismatch if this Person class does not implement a paintable interface.

But perhaps the most important reason that declaring an object as an interface is important is that we want to expose the methods that were defined in the interface , and not the public methods that were defined on separate classes, as we saw before.

 Dim square_1 As IDrawable 

enter image description here

We are not only sure that square_1 has a draw method, but also ensures that only methods defined by IDrawable are detected.
For a square, the advantage of this may not be immediately clear, but let's take a look at the analogy from the Java collection framework, which is much clearer.

Imagine that you have a common interface called IList , which defines the set of methods used for different types of lists. Each type of list is a specific class that implements the IList interface, defines their own behavior, and possibly adds more native methods from above.

We declare the list as follows:

 dim myList as IList 'Declare as the interface! set myList = new ArrayList 'Implements the interface of IList only, ArrayList allows random (index-based) access 

In the above code, declaring the list as IList ensures that you will not use methods specific to ArrayList, but only those methods that are specified by the interface. Imagine you declared a list as follows:

 dim myList as ArrayList 'We don't want this 

You will have access to the public methods defined in the ArrayList class. Sometimes this may be desirable, but often we just want to use the internal behavior of the class and not be defined by the public methods of the class.
The advantage becomes clear if we use this ArrayList 50 more times in our code, and suddenly we find that we are better off using a LinkedList (which allows for specific internal behavior associated with this type of list).

If we run the interface, we can change the line:

 set myList = new ArrayList 

to:

 set myList = new LinkedList 

and none of the other codes will break, as the interface ensures that the contract is completed, i.e. only the public methods defined in IList are used, therefore various types of lists are replaceable over time.

The last thing (perhaps a lesser known behavior in VBA) is that you can provide a default interface

We can define an interface as follows:

IDrawable:

 Public Function draw() Debug.Print "Draw interface method" End Function 

and a class that also implements the draw method:

cSquare:

 implements IDrawable Public Function draw() Debug.Print "Draw square method" End Function 

We can switch between implementations as follows:

 Dim square_1 As IDrawable Set square_1 = New IDrawable square_1.draw 'Draw interface method Set square_1 = New cSquare square_1.draw 'Draw square method 

This is not possible if you declare the variable as cSquare.
I cannot immediately come up with a good example when this can be useful, but technically it is possible if you test it.

+10
Oct 11 '15 at 0:55
source share

We must implement all the interface methods in the class that it uses.

class cCircle

 Option Explicit Implements cShape Public myRadius As Double Public Function getDiameter() getDiameter = 2 * myRadius End Function Public Function getArea() getArea = Application.WorksheetFunction.Pi() * (myRadius ^ 2) End Function ''Inertia around the X axis Public Function getInertiaX() getInertiaX = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function ''Inertia around the Y axis ''Ix = Iy in a circle, technically should use same function Public Function getIntertiaY() getIntertiaY = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function Public Function toString() toString = "This is a radius " & myRadius & " circle." End Function Private Function cShape_getArea() As Variant End Function Private Function cShape_getInertiaX() As Variant End Function Private Function cShape_getIntertiaY() As Variant End Function Private Function cShape_toString() As Variant End Function 

classRectangle Class

 Option Explicit Implements cShape Public myLength As Double ''going to treat length as d Public myWidth As Double ''going to treat width as b Private getIntertiaX As Double Public Function getArea() getArea = myLength * myWidth End Function Public Function getInertiaX() getIntertiaX = (myWidth) * (myLength ^ 3) End Function Public Function getIntertiaY() getIntertiaY = (myLength) * (myWidth ^ 3) End Function Public Function toString() toString = "This is a " & myWidth & " by " & myLength & " rectangle." End Function Private Function cShape_getArea() As Variant End Function Private Function cShape_getInertiaX() As Variant End Function Private Function cShape_getIntertiaY() As Variant End Function Private Function cShape_toString() As Variant End Function 

cShape class

 Option Explicit Public Function getArea() End Function Public Function getInertiaX() End Function Public Function getIntertiaY() End Function Public Function toString() End Function 

enter image description here

+8
Oct 15 '13 at 4:13
source share

A very interesting article to understand why and when the interface can be useful! But I think your last example on the default implementation is wrong. The first call to the draw method of square_1 created as IDrawable correctly prints the result you give, but the second call to the draw method of square_1 created as cSquare is incorrect, nothing is printed. 3 different methods come into play:

IDrawable.cls:

 Public Function draw() Debug.Print "Interface Draw method" End Function 

cSquare.cls:

 Implements IDrawable Public Function draw() Debug.Print "Class Draw method" End Function Public Function IDrawable_draw() Debug.Print "Interfaced Draw method" End Function 

Standard module:

 Sub Main() Dim square_1 As Class6_Methods_IDrawable Set square_1 = New Class6_Methods_IDrawable Debug.Print "square_1 : "; square_1.draw Dim square_2 As Class6_Methods_cSquare Set square_2 = New Class6_Methods_cSquare Debug.Print "square_2 : "; square_2.draw Dim square_3 As Class6_Methods_IDrawable Set square_3 = New Class6_Methods_cSquare Debug.Print "square_3 : "; square_3.draw End Sub 

Results in:

 square_1 : Interface Draw method square_2 : Class Draw method square_3 : Interfaced Draw method 
0
Jul 29 '17 at 20:45
source share



All Articles