I've tried to create a vb scripts class with a constant and got 800A03EA error. It's it a VBS bug? Isn't it an OOP fundamental rule?
Class customer
' comment it const and its works
const MAX_LEN=70
Private Name
Private Sub Class_Initialize
Name = ""
End Sub
' name property.
Public Property Get getName
getName = Name
End Property
Public Property Let letName(p_name)
Name = p_name
End Property
end class
The documentation lists all statements that are allowed in the context of classes. Const isn't among them, so it's not supported. You can work around the issue by using private member variables that you initialize during instantiation (i.e. in Class_Initialize):
Class customer
Private MAX_LEN
Private Name
Private Sub Class_Initialize
MAX_LEN = 70
Name = ""
End Sub
...
End Class
If instances of the class should expose this value, you could implement it as a read-only property:
Class customer
Private MAX_LEN
Private Sub Class_Initialize
MAX_LEN = 70
End Sub
'read-only property, so no "Property Let/Set"
Public Property Get MaxLength
MaxLength = MAX_LEN
End Property
...
End Class
However, as Ekkehard.Horner pointed out correctly, the value could still be changed by object-internal code. If immutability is the primary requirment for this value you should implement it as a global constant.
I agree with Ansgar Wiechers's answer, but would like to propose another option.
If immutability is more important than performance, you could put the value directly in the Get and use the property to refer to the value instead of a class-level variable.
Class customer
'read-only property, so no "Property Let/Set"
Public Property Get MaxLength
MaxLength = 70
End Property
...
End Class
A Private variable (perhaps with a getter) gives you a value that is read-only from the outside of the class, but class internal code can still change that value.
So using a global Const (perhaps with a 'namespace' name part) may be a better workaround in cases where the constness is most important.
Related
I have already asked a question here where I basically require an instance of a base class to be converted into a subclass (or a new instance of the subclass to be created using the instance of the base class' properties). The conclusion seems to be that the best way to do this is to manually assign every property I need to transfer in the constructor of the base class.
While this is feasible in some cases, it certainly is not when there are many properties to transfer, or when the base class is subject to change — every time you add a property to the base class, the constructor needs to be changed too, so this solutions is inelegant.
I have searched online, and can't see any reason for why this kind of type-casting isn't implemented. The arguments I have seen so far describe this operation to 'not make any sense' (making a minivan from a car was an analogy I saw), question what to do about the non-inherited variables in the subclass, or claim that there must be some better solution for what was trying to be achieved.
As far as I can see, the operation doesn't need to 'make sense' as long as it's useful, so that isn't much of a good reason. What's wrong with adding a few more properties (and perhaps methods/overriding them) to change an instance into a subclass? In the case of the non-inherited variables, that can simply be solved by allowing this kind of type-cast only a constructor is added to the subclass or by just simply setting them to their default values. After all, constructors usually call MyBase.New(...) anyway. What's the difference between using the constructor of the base (essentially creating a new instance of the base) and using an instance which is already initialised? Lastly, I don't think the third argument is well-justified — there are times when all of the other solutions are inelegant.
So finally, is there any other reason for why this kind of casting isn't allowed, and is there an elegant way to circumvent this?
Edit:
Since I don't know a lot about this topic, I think I meant to say 'convert' rather than 'cast'. I'll also add an example to show what I'm trying to succeed. The conversion would only be allowed at the initialisation of the Subclass:
Class BaseClass
Dim x as Integer
Dim y as Integer
End Class
Class Subclass1 : Inherits BaseClass
Dim z as Integer
Sub New(Byval value As Integer)
'Standard initialisation method
MyBase.New()
z = value
End Sub
Sub New(Byval value As Integer, Byval baseInstance As BaseClass)
'Type conversion from base class to subclass
baseInstance.passAllproperties()
'This assigns all properties of baseInstance belonging to BaseClass to Me.
'Properties not in BaseClass (eg. if baseInstance is Subclass2) are ignored.
z = value
End Sub
End Class
Class Subclass2 : Inherits BaseClass
Dim v As Integer
End Class
What you describe is not casting. Have you ever heard the expression"to cast something in a different light"? It means to look at the same thing in a different way or to make the same thing look different. That is the exact way that the term "cast" is used in programming. When you cast, you do NOT change the type of the object but only the type of the reference used to access the object. If you want to cast from a base type to a derived type then the object you're referring to has to actually be that derived type. If it's not then you're not performing a cast but rather a conversion.
So, why can't you convert an instance of a base type to an instance of a derived type. Well, why would you be able to? Yes, it's something that might save writing a bit of code on occasion but does it actually make sense? Let's say that you have a base type with one property and a derived type that adds another property. Let's also say that that derived type has constructors that require you to provide a value for that second property. You're suggesting that the language should provide you with a way to magically convert an instance of the base class into an instance of the derived class, which would mean it would have to slow you to circumvent that rule defined by the author via the constructors. Why would that be a good thing?
Use System.Reflection to iterate over properties and fields of the base class and apply them to the derived class. This example includes a single public property and single public field, but will also work with multiple private/protected properties and fields. You can paste the entire example into a new console application to test it.
Imports System.Reflection
Module Module1
Sub Main()
Dim p As New Parent
p.Property1 = "abc"
p.Field1 = "def"
Dim c = New Child(p)
Console.WriteLine("Property1 = ""{0}"", Field1 = ""{1}""", c.Property1, c.Field1)
Console.ReadLine()
End Sub
Class Parent
Public Property Property1 As String = "not set"
Public Property Field1 As String = "not set"
End Class
Class Child
Inherits Parent
Public Sub New(myParent As Parent)
Dim fieldInfo = GetType(Parent).GetFields(BindingFlags.NonPublic _
Or BindingFlags.Instance)
For Each field In fieldInfo
field.SetValue(Me, field.GetValue(myParent))
Next
Dim propertyInfo = GetType(Parent).GetProperties(BindingFlags.NonPublic _
Or BindingFlags.Instance)
For Each prop In propertyInfo
prop.SetValue(Me, prop.GetValue(myParent))
Next
End Sub
End Class
End Module
Output:
Property1 = "abc", Field1 = "def"
This solution is automated, so you won't need to change anything when adding or removing properties and fields in the base class.
In general, because of this:
Class TheBase
End Class
Class Derived1 : TheBase
Sub Foo()
End Sub
End Class
Class Derived2 : TheBase
Sub Bar()
End Sub
End Class
Sub Main()
Dim myDerived1 As New Derived1
' cast derived to base
Dim myTheBase = CType(myDerived1, TheBase)
' cast base to derived?
' but myTheBase is actually a Derived1
Dim myDerived2 As Derived2 = CType(myTheBase, Derived2)
' which function call would you like to succeed?
myDerived2.Foo()
myDerived2.Bar()
End Sub
I'm looking for a way to 'chain class methods', for example the Range object can do things like "Range.Borders.Color", I guess that the Borders part is it's own class which is being accessed by the Range class but I have no idea how to implement something similar with my own classes - Nor do I even know what this is called and after hours of searching I think I might slowly be un-learning VBA.
Can anybody either a) Provide code which I could look at to replicate or b) Tell me what this is called and maybe even nudge me in a helpful direction?
As I know asking for code without providing any makes me look like a dick, consider the following pseudo-code. I know it's horrific but it might help me make any sense:
main ------------------------------------------------------------------------
Dim obj as class1
set obj = new class1
obj.Target = Range("A1:B5")
obj.Borders.Add
'A1:B5 put into modRange then given borders
class1 ------------------------------------------------------------------------
Private modRange as range
Public Property Let Target(newTarget as Range)
set modRange = newTarget
End Property
Public Property Borders()
Public Sub Add()
'Code to add borders to modRange
End Sub
Public Sub Remove()
'Code to remove borders from modRange
End Sub
End Property
I know this is not how the actual code would look. but as I don't know the syntax this is the closest thing I can imagine. I guess the real thing would have class1 linking to other class modules. Maybe.
As a side note. If I did have a class called "Borders" (I probably wont) as part of this class 1 object, would it conflict with the Borders portion of the Range object as well as it has a similar name? Or will the Private scope save the day?
(The .Borders.Add/Remove is a bit ridiculous to have as a class I know, I'm really only after the syntax - Honest)
To have complex properties of an object, you need to create a new class and then create an instance of that class in the parent class. So if you want to have something like Class1.Borders.Add(), you'd first have to create a new CBorders class (I used to prepend C to my class names in VB6 / VBA to avoid name collisions). Something like:
'- in class CBorder
Private m_lColor As Long
Public Property Get Color() As Long
Color = m_lColor
End Property
Public Property Let Color(ByVal lNewColor As Long)
m_lColor = lNewColor
End Property
Public Sub Reset()
m_lColor = 0
End Sub
...
Then inside Class1, you'd have something like this:
Private m_oBorder As CBorder
Private Sub Class_Initialize()
...
Set m_oBorder = New CBorder
...
End Sub
Public Property Get Border() As CBorder
Set Border = m_oBorder
End Property
...
Then you can do this:
Dim obj As Class1
Set obj = New Class1
obj.Borders.Color = ...
...
Notice how the Borders property of Class1 is accessed as a member of the obj instance and then how the Color property of the CBorder class is used. Creating these values as properties is what lets you chain these calls together.
You'd need error checking and validation code as well - I left those out to keep the example short.
Another solution for this is to just return Me to make it chainable.
class module: CChaining
Using Functions for Target and Borders returning Me to enable chaining. Using Subs for Add and Remove to "finish" the chain.
Private modRange As Range, modRangeBorders As Object
Public Function Target(rng As Range)
Set modRange = rng
Set Target = Me
End Function
Public Function Borders()
Set modRangeBorders = modRange.Borders
Set Borders = Me
End Function
Public Sub Add()
modRangeBorders.LineStyle = xlContinuous
End Sub
Public Sub Remove()
modRangeBorders.LineStyle = xlNone
End Sub
Testing the class in a module
Sub testing()
Dim obj As New CChaining
obj.Target(Range("A1:B5")).Borders.Add
'now the target and property (Borders) is set and you could do this
'obj.Remove
End Sub
Pretty nice ... hmm, with this it would be possible to build a library like in other languages (javascript > jQuery) to make using Excel VBA much easier.
I have been trying to implement a very simple tree structure in VBA for some basic text parsing needs. The relevant code follows.
Private pLeaves() As CTree
Private numLeaves As Integer
Private leavesLen As Integer
Private iterate As Integer
Private pParent As CTree
Public pValue As Object
Public Sub class_initialize()
ReDim pLeaves(10)
leavesLen = 10
numLeaves = 0
Set pParent = Nothing
iterate = 0
Set pValue = Nothing
End Sub
Public Sub Class_terminate()
'We'll leave it empty for now
'if it looks like it's not working right we might change it
End Sub
Public Property Get Parent() As CTree
Parent = pParent
End Property
Public Property Get Leaves() As CTree
Leaves = pLeaves
End Property
Private Property Set Parent(ByRef p As CTree)
Set pParent = p
End Property
Private Property Set value(ByRef value As Object)
Set pValue = value
End Property
Public Sub Add_Leaf(ByRef value As Object)
Dim n As Integer
n = numLeaves
If numLeaves >= leavesLen Then
ReDim Preserve pLeaves(leavesLen + 10)
leavesLen = leavesLen + 10
End If
Set pLeaves(n) = New CTree
Set pLeaves(n).Parent = Me
Set pLeaves(n).value = value
End Sub
Public Function DFFind_Leaf(value As Object) As CTree
Dim i As Integer
If pValue = value Then
Set DFFind_Leaf = Me
Return
End If
If numLeaves = 0 Then
Set DFFind_Leaf = Nothing
Return
End If
For i = 0 To numLeaves
Set DFFind_Leaf = pLeaves(i).DFFind_Leaf(value)
If DFFind_Leaf <> Nothing Then
Return
End If
Next i
Set DFFind_Leaf = Nothing
Return
End Function
When I try to call the Add_Leaf function with an object though I end up getting a
method or data member not found error from VBA on the line where I'm trying to
set pLeaves(n).Parent= me
Any idea what the reason for that might be?
Edit: ok I figured out how to fix this. Changing the set property Parent(...) from private
to public fixed the problem. So apparently I don't quite understand what private exactly does in vba. If someone wants to explain how to do this without essentially exposing the variables for anyone to set as they want I would be grateful.
Private tells the VBA interpreter that the scope of the variable is restricted to the class/module only. You can therefore access it only inside of the original class, but not from the outside. Publicon the other hand allows you to access the variable from the outside.
However, note that when working with classes, it is strongly discouraged to publicly expose any variables. Instead it is best practice to expose so called properties, using the SET PROPERTYand GET PROPERTY keywords.
I.e. instead of
Public Amount as Double
It is often better to use this approach:
Private mAmount as Double
Property Set Amount(amt as Double) 'Note that by default, properties are public
If amt > 0 Then
mAmount = amt
Else
mAmount = 0
End If
End Property
Property Get Amount as Double
Amount = mAmount
End Property
As you can see from this example, the advantage is that you can actually add additional verifications/modifications and therefore ensure the consistency.
In your example, you could consider to simply provide a method, i.e. a public sub, that adds a leave and takes care of all the settings, e.g.
Public Sub AddLeave(strName as String)
Dim objLeave as New CTree
objLeave.Init strName, Me
mLeaves.Add objLeave, strName
End Sub
Public Sub Init(strName as String, objParent as CTree)
Me.Name = strName
Set Me.Parent = objParent
End Sub
Ok guys, well i'd like to achieve an effect of nested enumeration for easy grouping some constant strings. Something like the pseudo code bellow:
Enum gKS
Colby = "Hello"
Hays = "World"
end Enum
Enum gMA
Dodge = "Seven"
Muscatine = "Ports"
end Enum
Enum gCountry
north as gMA
south as gKS
end Enum
Public USA as gCountry
So the code bellow should output a "Seven" message:
sub dol()
msgbox USA.north.Dodge
end sub
I don't want use types or classes because no initialisation is needed since all values are know (constants as i said).
Any suggestions?
thx.
Classes are the way to go on this. Enums are simply long values, where limited selection is needed. This will allow for the greatest flexibility with your objects, in case you need these objects to have other function/subs.
Here is a simple layout:
gCountry Class:
Public North As gMA
Public South As gKS
Private Sub Class_Initialize()
Set North = New gMA
Set South = New gKS
End Sub
gKS Class:
Public Property Get Colby() As String
Colby = "Hello"
End Property
Public Property Get Hays() As String
Hays = "World"
End Property
gMA Class:
Public Property Get Dodge() As String
Dodge = "Seven"
End Property
Public Property Get Muscatine() As String
Muscatine = "Ports"
End Property
Testing It:
Public Sub TestIt()
Dim USA As New gCountry
MsgBox USA.North.Dodge
End Sub
I don't believe you're going to be able to do embedded enums the way you are hoping, because enums are considered primitives in the CLR (source). You may as well try to embed ints within ints.
I realize you said you didn't want to use classes, but this is the sort of situation static classes are meant to fill in the .NET world. It will be easily and arbitrarily accessible with no initialization and quick when compiled. This page has more information on statics if you're not familiar with them. You should be able to do whatever you need to do to get the information set up the way you want within that class, be it multiple static classes, a hash table, a multi-dimensional array, or whatever have you.
THX,
So. i've decided to solve that issue using types:
Public Type fCAOCC
itRGI As String
...
End Type
Public Type fCAOBF
itRGI As String
ibEnviar As String
...
End Type
Public Type gTELAS
CAOBF As fCAOBF
CAOCC As fCAOCC
...
End Type
Public CSI As gTELAS
Sub iniGLOBALS()
CSI.CAOBF.itRGI = "DIVNRRGILIG"
CSI.CAOBF.ibEnviar = "DUMMYNAME1"
CSI.CAOCC.itRGI = "Hello"
...
End Sub
And thats ready for later use on code...
cya
I have created two classes as interface / implementation classes, and wish to pass a particular example of one class to a method in the other. The definitions are as follows...
Class BigInt...
Option Explicit
Public Sub dothing(ByRef passed_object As MyInt)
End Sub
and an implementation BigImplementation...
Option Explicit
Implements BigInt
Public Sub BigInt_dothing(ByRef passed_obj As MyInt)
Dim i As Integer
i = passed_obj.getprop
End Sub
The class I am planning to pass is...
Option Explicit
Public Property Get getprop() As Integer
End Property
Public Property Let letprop(ByVal myval As Integer)
End Property
implemented as MyImplementation thus...
Option Explicit
Implements MyInt
Private myval As Integer
Public Property Get myint_getprop() As Integer
myint_getprop = myval
End Property
Public Property Let myint_letprop(ByVal passed_int As Integer)
myval = passed_int
End Property
I am then driving this with the following snippet of code:-
Private Sub Command_Click()
Dim myobj As MyInt
Set myobj = New MyImplementation
Dim mybigobj As BigInt
Set mybigobj = New BigImplementation
myobj.letprop = 1
Call mysub(myobj)
mybigobj.dothing (myobj) ' Line with problem
End Sub
Private Sub mysub(ByVal passed_obj As MyInt)
Dim i As Integer
i = passed_obj.getprop
End Sub
When the execution reaches the line marked, I get run-time error 438 - Object doesn't support property or method. The call to the ordinary function mysub works perfectly. Does anyone know what I am doing wrong and what I need to do to fix this?
Use either
mybigobj.dothing myobj
or
Call mybigobj.dothing(myobj)
Putting extra parentheses around a reference evaluates its default property and passes it's value as actual argument.
mybigobj.dothing requires its parameter to be a MyInt
Public Sub BigInt_dothing(ByRef passed_obj As MyInt)
Dim i As Integer
i = passed_obj.getprop
End Sub
You're passing a MyImplementation
Set myobj = New MyImplementation
So possiblly something like (my vb is rusty)
mybigobj.dothing (myobj.myint_getprop())