Is it possible (using COM and regasm.exe) to have a vba function call a vb.net function - which creates the class in vb.net and then passes the class back to vba, where it is recognised as a vba class?
In VBA, I can work with classes by using Insert>Class Module. I have set up a function that creates a class.
Private length As Double
Private height As Double
Public Sub init(ByRef hgt As Double)
height = hgt
length = dbl_height()
End Sub
Public Function dbl_height()
dbl_height = height * 2
End Function
I can initialize it accordingly using this function:
Public Function CreateClassFunction(foo As Integer)
Dim my_rect As Rectangle
Set my_rect = New Rectangle
my_rect.init (foo)
Set CreateClassFunction = my_rect
End Function
I can also do the same thing in vb.net with virtually identical code.
Public Class Rectangle
Private length As Double
Private height As Double
Public Sub init(ByRef hgt As Double)
height = hgt
length = dbl_height()
End Sub
Public Function dbl_height()
dbl_height = height * 2
End Function
End Class
where this vb.net function creates the class:
Public Function CreateClassFunction(foo As Integer) As Rectangle
Dim my_rect As Rectangle
my_rect = New Rectangle
my_rect.init(foo)
CreateClassFunction = my_rect
End Function
I can pull in a Variant/Object/Rectangle into vba using:
Function MyCreateClass(a As Double)
Dim classLib As New MyAnalytics.Class1
Set MyCreateClass = classLib.CreateClassFunction(a)
End Function
However this object does not have the height or length variables. (It says "no variables" on the watch window)
Edit:
Amended code as per Mat's Mug answer:
Public Class Rectangle
Private plength As Double
Private pheight As Double
Public Property length() As Double
Get
Return plength
End Get
Set(ByVal value As Double)
plength = value
End Set
End Property
Public Property height() As Double
Get
Return pheight
End Get
Set(ByVal value As Double)
pheight = value
End Set
End Property
Public Sub init(ByRef hgt As Double)
height = hgt
length = dbl_height()
End Sub
Public Function dbl_height()
dbl_height = height * 2
End Function
End Class
and testing in VBA:
Function MyCreateClass(a As Double)
Dim classLib As New MyAnalytics.Class1
Set MyCreateClass = classLib.CreateClassFunction(a)
Debug.Print MyCreateClass.Height()
Debug.Print MyCreateClass.length()
MyCreateClass.Height = 30
MyCreateClass.length = 20
Debug.Print MyCreateClass.Height()
Debug.Print MyCreateClass.length()
MyCreateClass.init (100)
Debug.Print MyCreateClass.Height()
Debug.Print MyCreateClass.length()
End Function
It won't be recognized as a VBA class - it's not a VBA class, but a COM object.
Your Rectangle class has private fields. Private fields are, well, Private. This is, roughly, what VBA sees:
Public Class Rectangle
Sub init(ByRef hgt As Double)
Function dbl_height()
End Class
Where are the fields?
Private length As Double
Private height As Double
You haven't exposed them - as far as VBA goes, they don't exist.
Now, you could make them Public - but then you would be breaking encapsulation by exposing fields; don't do that!
Expose property getters instead, and setters if you want VBA code to be able to change the Length and Height properties of a Rectangle instance.
Related
I am working on a script to help me create geometry in 3D software based on user input and I wanted to approach the problem with classes. I have 3 levels of structures, points, curves and surfaces so I want to create a class for each, where the next level structure inherits the lower one.
So for example class cPoint have 4 properties: x,y,z,id. Further, the class cCurve has only 2 properties: id and points, and the same for surfaces.
Now my question is: I implemented class cPoint as follows:
Private x_ As Double
Private y_ As Double
Private z_ As Double
Private id_ As Long
Public Property Let X(ByVal value As Double)
x_ = value
End Property
Public Property Let Y(ByVal value As Double)
y_ = value
End Property
Public Property Let Z(ByVal value As Double)
z_ = value
End Property
Public Property Let ID(ByVal value As Long)
id_ = value
End Property
Public Property Get X() As Double
X = x_
End Property
Public Property Get Y() As Double
Y = y_
End Property
Public Property Get Z() As Double
Z = z_
End Property
Public Property Get ID() As Long
ID = id_
End Property
and everything is fine here. And here I have class cCurve implemented:
Implements cPoint
Private id_ As Long
Private point_ As Collection
Public Property Let ID(ByVal value As Long)
id_ = value
End Property
Public Property Set point(ByVal value As Collection)
Set point_ = value
End Property
Public Property Get ID() As Long
ID = id_
End Property
Public Property Get point() As Collection
Set point = point_
End Property
But here, when I try to run the code I get prompted with the following error:
Object module needs to implement 'X' for interface 'cPoint'
I think I know what it means, but I have no clue how to implement it. Is my approach even correct?
I'd highly appreciate any guidance in this manner.
VBA does not support inheritance. The only way to mimic inheritance is to implement your Point class and at the same time have a private instance of the Point class in your curve class. This is what your "Point" class would look like (I used "GeometryPoint" as the name of the class because Excel already has a "Point" class created):
Option Explicit
Private Type TState
ID As Long
X As Double
Y As Double
Z As Double
End Type
Private This As TState
Public Property Let ID(ByVal Value As Long)
This.ID = Value
End Property
Public Property Get ID() As Long
ID = This.ID
End Property
Public Property Let X(ByVal Value As Double)
This.X = Value
End Property
Public Property Get X() As Double
X = This.X
End Property
Public Property Let Y(ByVal Value As Double)
This.Y = Value
End Property
Public Property Get Y() As Double
Y = This.Y
End Property
Public Property Let Z(ByVal Value As Double)
This.Z = Value
End Property
Public Property Get Z() As Double
Z = This.Z
End Property
And this is what your "Curve" class would look like:
Option Explicit
Implements GeometryPoint
Private Type TState
Base As GeometryPoint
ID As Long
Points As Collection
X As Double
Y As Double
Z As Double
End Type
Private This As TState
Public Property Let GeometryPoint_ID(ByVal Value As Long)
This.Base.ID = Value
End Property
Public Property Get GeometryPoint_ID() As Long
GeometryPoint_ID = This.Base.ID
End Property
Public Property Let GeometryPoint_X(ByVal Value As Double)
This.Base.X = Value
End Property
Public Property Get GeometryPoint_X() As Double
GeometryPoint_X = This.Base.X
End Property
Public Property Let GeometryPoint_Y(ByVal Value As Double)
This.Base.Y = Value
End Property
Public Property Get GeometryPoint_Y() As Double
GeometryPoint_Y = This.Base.Y
End Property
Public Property Let GeometryPoint_Z(ByVal Value As Double)
This.Base.Z = Value
End Property
Public Property Get GeometryPoint_Z() As Double
GeometryPoint_Z = This.Base.Z
End Property
Public Property Set Points(ByVal Value As Collection)
Set This.Points = Value
End Property
Public Property Get Points() As Collection
Set Points = This.Points
End Property
Private Sub Class_Initialize()
Set This.Base = New GeometryPoint
End Sub
Private Sub Class_Terminate()
Set This.Base = Nothing
End Sub
In VBA, when you "Implement" a class you need to list all of the members (properties, methods) of that class. In your case you received an error because you didn't list the X,Y,Z properties of the cPoint class that you were implementing.
Is the "Curve" class supposed to have X,Y,Z properties as well? If not then you shouldn't implement the Point class. If the Curve class is just a class that stores a collection of Points then your Curve class should look like this:
**Note: This code was edited to include the "Class_Initialize" method which sets the "Points" member as a new collection once the Curve class is instantiated.
Option Explicit
Private Type TState
ID As Long
Points As Collection
End Type
Private This As TState
Public Property Let ID(ByVal Value As Long)
This.ID = Value
End Property
Public Property Get ID() As Long
ID = This.ID
End Property
Public Property Set Points(ByVal Value As Collection)
Set This.Points = Value
End Property
Public Property Get Points() As Collection
Set Points = This.Points
End Property
Private Sub Class_Initialize()
Set This.Points = New Collection
End Sub
Private Sub Class_Terminate()
Set This.Points = Nothing
End Sub
Finally, adding a new Point object to the "Points" member in the Curve class would look something like this:
Dim NewPoint As GeometryPoint
Dim NewCurve As Curve
Set NewPoint = New GeometryPoint
With NewPoint
.X = 1
.Y = 2
.Z = 3
End With
Set NewCurve = New Curve
With NewCurve.Points
.Add NewPoint
End With
I'm doing an IT course for college and one of the assignments requires you to create a BMI calculator in Visual Basic with the use of object orientated techniques. I'm not a very good programmer and thus I'm stuck on a problem I keep receiving. The code I'm using was given to me by someone who claimed it works, however when I run the program any results given are NaN.
Anyone have an idea as to what is wrong with the code in order to give me this result?
Here is the code I'm using:
Public Class Form1
Private Sub Button_Calculate_Click(sender As Object, e As EventArgs) Handles
Button_Calculate.Click
Dim height As Double = Double.Parse(TextBox_Height.Text)
Dim weight As Double = Double.Parse(TextBox_Weight.Text)
bmi.SetWeight(weight)
bmi.SetHeight(height)
TextBox_BMI.Text = Format(bmi.GetBMI(), "0.00")
End Sub
Private bmi As New BMI
End Class
In a separate class:
Public Class BMI
Public Function GetBMI()
Return (weight / (height ^ 2))
End Function
Public Function GetWeight()
Return weight
End Function
Public Function GetHeight()
Return height
End Function
Public Function SetWeight(_weight As Double)
Return weight = _weight
End Function
Public Function SetHeight(_height As Double)
Return height = _height
End Function
Private weight As Double
Private height As Double
End Class
There are a few problems with your (meaning kushlord420) solution.
Visual Basic code in case insensitive so bmi is the same as BMI
You never use the Form level variable bmi so delete.
You tried to write a custom constructor but in vb.net it is Sub New
You are converting the values in the weight and height text boxes to Double but your properties are type Single. Actually this should
be Single.TryParse but that is for another day.
Functions in vb.net must have a data type for the return value. This is provided in the first line of the function. Since you are
using Format on the return value I made the value a String and
converted the return value.
Fixed the constructor parameters to avoid ambiguity.
Sub Button_Calculate_Click(sender As Object, e As EventArgs) Handles Button_Calculate.Click
Dim bmi As New BMI(CSng(TextBox_Weight.Text), CSng(TextBox_Height.Text))
TextBox_BMI.Text = Format(bmi.GetBMI(), "0.00")
End Sub
Public Class BMI
Public Function GetBMI() As String
Return (Weight / (Height ^ 2)).ToString
End Function
Public Property Weight As Single
Public Property Height As Single
Public Sub New(wght As Single, hght As Single)
Weight = wght
Height = hght
End Sub
End Class
You really need something more like this:
Public Class BMI
Public Function GetBMI() As Double
Return (weight / (height ^ 2))
End Function
Public Property Weight As Double
Public Property Height As Double
Public Sub New(weight As Double, height As Double)
Me.Weight = weight
Me.Height = height
End Sub
End Class
Public Class Form1
Private Sub Button_Calculate_Click(sender As Object, e As EventArgs) Handles Button_Calculate.Click
Dim bmi As New BMI(CDbl(TextBox_Weight.Text), CDbl(TextBox_Height.Text))
TextBox_BMI.Text = Format(bmi.GetBMI(), "0.00")
End Sub
End Class
Or better yet, this:
Public Class BMI
Public Property Weight As Double
Public Property Height As Double
Public ReadOnly Property BMI As Double
Get
Return (Weight / (Height ^ 2))
End Get
End Property
Public Sub New()
End Sub
Public Sub New(weight As Double, height As Double)
Me.Weight = weight
Me.Height = height
End Sub
End Class
With the help of a friend, figured out my problem.
If anyone is curious, here is the code that made it work:
Public Class Form1
Sub Button_Calculate_Click(sender As Object, e As EventArgs) Handles
Button_Calculate.Click
Dim bmi As New BMI With {.Weight = CDbl(TextBox_Weight.Text), .Height =
CDbl(TextBox_Height.Text)}
TextBox_BMI.Text = Format(bmi.GetBMI(), "0.00")
End Sub
Private bmi As New BMI
End Class
And:
Public Class BMI
Public Function GetBMI()
Return (weight / (height ^ 2))
End Function
Property Weight As Single
Property Height As Single
Public Sub BMI(weight As Single, height As Single)
Me.Weight = weight
Me.Height = height
End Sub
End Class
I have two classes of shapes and an interface. After I instantiate objects from each class and set their properties I add them to a collection. That's pretty straightforward. Then I declare a variable of type MyInterface and loop through the collection to add each shape. But there is an additional property for each type of shape that I want to set but which are not part of the interface. Is there a way to do this? I think other languages call this type casting but I am not sure. Does VBA support this? Any help is appreciated. My code is below:
Interface (iShape)
Option Explicit
Public Property Let Top(value As Long)
End Property
Public Property Get Top() As Long
End Property
Public Property Let Left(value As Long)
End Property
Public Property Get Left() As Long
End Property
Public Property Let Width(value As Long)
End Property
Public Property Get Width() As Long
End Property
Public Property Let Height(value As Long)
End Property
Public Property Get Height() As Long
End Property
Public Function Draw(obj As Worksheet) As Excel.Shape
End Function
Class (cDiamond)
Option Explicit
Private pTop As Long
Private pLeft As Long
Private pWidth As Long
Private pHeight As Long
Private pColor As Long
Implements iShape
'====================Properties====================
Public Property Let Top(value As Long)
pTop = value
End Property
Public Property Get Top() As Long
Top = pTop
End Property
Public Property Let Left(value As Long)
pLeft = value
End Property
Public Property Get Left() As Long
Left = pLeft
End Property
Public Property Let Width(value As Long)
pWidth = value
End Property
Public Property Get Width() As Long
Width = pWidth
End Property
Public Property Let Height(value As Long)
pHeight = value
End Property
Public Property Get Height() As Long
Height = pHeight
End Property
Public Property Let Color(value As Long)
pColor = value
End Property
Public Property Get Color() As Long
Color = pColor
End Property
'====================Methods====================
Public Function Draw(obj As Worksheet) As Excel.Shape
Set Draw = obj.Shapes.AddShape(msoShapeFlowchartOffpageConnector, Me.Left, Me.Top, Me.Width, Me.Height)
End Function
'====================Interface====================
Private Property Get iShape_Height() As Long
iShape_Height = Height
End Property
Private Property Let iShape_Height(RHS As Long)
Height = RHS
End Property
Private Property Get iShape_Left() As Long
iShape_Left = Left
End Property
Private Property Let iShape_Left(RHS As Long)
Left = RHS
End Property
Private Property Get iShape_Top() As Long
iShape_Top = Top
End Property
Private Property Let iShape_Top(RHS As Long)
Top = RHS
End Property
Private Property Get iShape_Width() As Long
iShape_Width = Width
End Property
Private Property Let iShape_Width(RHS As Long)
Width = RHS
End Property
Private Function iShape_Draw(obj As Worksheet) As Shape
Set iShape_Draw = Draw(obj)
End Function
Class (cTextbox)
For the sake of brevity, this class is identical to cDiamond except it has a Caption property instead of a Color property.
Module (mTest)
Option Explicit
Private Sub Test()
Dim wks As Excel.Worksheet
Set wks = ActiveSheet
Dim c As Collection
Set c = New Collection
Dim d1 As cDiamond
Set d1 = New cDiamond
d1.Top = 10
d1.Left = 10
d1.Height = 25
d1.Width = 25
d1.Color = RGB(255, 0, 0)
c.Add d1
Dim d2 As cDiamond
Set d2 = New cDiamond
d2.Top = 50
d2.Left = 10
d2.Height = 25
d2.Width = 25
d2.Color = RGB(0, 255, 0)
c.Add d2
Dim t1 As cTextbox
Set t1 = New cTextbox
t1.Top = 90
t1.Left = 10
t1.Height = 25
t1.Width = 25
t1.Caption = "Textbox"
c.Add t1
Dim shp As iShape
For Each shp In c
shp.Draw wks
' I would like to set the color or caption properties depending on the type of shape in the collection.
Next shp
Set c = Nothing
End Sub
If I understand you correctly, (and I don't fully understand interfaces either), you should be able to do what you want by declaring shp as of type variant.
Dim shp
For Each shp in C ...
shp will then take on the type of either cDiamond or cTextbox depending on which is retrieved from the Collection. Then you will be able to retrieve or modify shp.color or shp.caption. You may also want to change Caption to datatype String in cTextBox
Declaring shp as object you will lose intellisense. Another technique would be to have Interface return a reference to the instance of object. Similarly to how OLEObject.Object returns the instance of the object that it is wrapping. In my example I use This to return the instance of the class.
In this way you will have intellisense for all the common properties and methods. We can also access the properties and methods that are unique to the classes that implement your interface using a With statement.
You'll need to first test check the type of the object. Then use a With statement to temporarily instantiate a new Object of that type and write your code inside the With statement .
If TypeOf shp Is cTextbox Then
With New cTextbox
Msgbox .Caption
End With
End If
Finally, you simply replace the New instance with the actual instance of the Object.
If TypeOf shp Is cTextbox Then
With shp.This
Msgbox .Caption
End With
End If
Interface (iShape)
Public Function This() As Object
End Function
Classes cTextbox & cDiamond
Public Function This() As Object
Set This = Me
End Function
Public Function iShape_This() As Object
Set iShape_This = This
End Function
mTest.Test
Dim shp As iShape
For Each shp In c
shp.Draw wks
If TypeOf shp Is cTextbox Then
With shp.This
MsgBox .Caption
End With
End If
' I would like to set the color or caption properties depending on the type of shape in the collection.
Next shp
How to call the base functions in vb.net?
Imports System.Data.Sql
Imports System.Data.SqlClient
Public Class Box
Public length As Double ' Length of a box
Public breadth As Double ' Breadth of a box
Public height As Double ' Height of a box
Public function setLength(ByVal len As Double)
length = len
End Sub
Public Sub setBreadth(ByVal bre As Double)
breadth = bre
End Sub
Public Sub setHeight(ByVal hei As Double)
height = hei
End Sub
Public Function getVolume() As Double
Return length * breadth * height
End Function
End Class
It says syntax error when I use MyBase to call the base functions
Public Class myChild : Inherits Box
'box 1 specification
MyBase.setLength(6.0)
MyBase.setBreadth(7.0)
MyBase.setHeight(5.0)
'box 2 specification
MyBase.setLength(12.0)
MyBase.setBreadth(13.0)
MyBase.setHeight(10.0)
'volume of box 1
volume = MyBase.getVolume()
Console.WriteLine("Volume of Box1 : {0}", volume)
'volume of box 2
volume = MyBase.getVolume()
End Class
You can't call MyBase from there as the object hasn't yet been constructed.
A better implementation would be:
Box.vb
Public Class Box
Private mLength As Double ' Length of a box
Private mBreadth As Double ' Breadth of a box
Private mHeight As Double ' Height of a box
Public Sub New(ByVal length As Double, ByVal breadth As Double, ByVal height As Double)
Me.mLength = length
Me.mBreadth = breadth
Me.mHeight = height
End Sub
Public Property Length As Double
Get
Return Me.mLength
End Get
Set(ByVal value As Double)
Me.mLength = value
End Set
End Property
Public Property Breadth As Double
Get
Return Me.mBreadth
End Get
Set(ByVal value As Double)
Me.mBreadth = value
End Set
End Property
Public Property Height As Double
Get
Return Me.mHeight
End Get
Set(ByVal value As Double)
Me.mHeight = value
End Set
End Property
Public Function getVolume() As Double
Return Length * Breadth * Height
End Function
End Class
Child.vb
Public Class Child : Inherits Box
Public Sub New(ByVal length As Double, ByVal breadth As Double, ByVal height As Double)
MyBase.New(length, breadth, height)
End Sub
End Class
Example
Sub Main()
Dim box1 As New Child(6.0, 7.0, 5.0)
Dim box2 As New Child(12.0, 13.0, 10.0)
Console.WriteLine("box1 volume is: {0}", box1.getVolume())
Console.WriteLine("box2 volume is: {0}", box2.getVolume())
End Sub
I created a class module Rect:
Public top As Single
Public left As Single
Public bottom As Single
Public right As Single
Public Sub Class_Initialize()
Me.top = 0
Me.bottom = 0
Me.left = 0
Me.right = 0
End Sub
And a factory function to create Rect objects:
Private Function n() As Rect
Dim r As New Rect
n = r
End Function
When I call function n() I got runtime error '91'. I don't know what is wrong...
For object variables, you need to use Set to assign them:
Set n = r
See e.g. What does the keyword Set actually do in VBA?