For every property in my class in ASP code I have to use this:
Public Property Get ItemsOnPage()
ItemsOnPage = m_ItemsOnPage
end Property
Public Property Let ItemsOnPage(inp)
m_ItemsOnPage = inp
End Property
This example is for the ItemsOnPage property. Is there any other way that I could use a subroutine somehow? I tried using:
sub subClassProperty(varProperty)
execute("Public Property Get " & varProperty & "()")
execute(varProperty & " = m_" & varProperty)
execute("end Property")
execute("Public Property Let " & varProperty & "(inp)")
execute("m_" & varProperty & " = inp")
execute("End Property")
end sub
but this sub I can not call from Class :-((
I believe you could use a public statement, which is a bit simpler syntax.
http://msdn.microsoft.com/en-us/library/72bd95z8%28v=VS.85%29.aspx
This illustrates a simple example
https://web.archive.org/web/20210506183450/http://www.4guysfromrolla.com/webtech/092399-1.2.shtml
VBScript is not a dynamic language in that sense. You can not modify the class at runtime.
I would suggest that instead of using properties, you use some common methods like SetProperty("propertyName", value) and GetProperty("propertyName) in the script that uses this class.
The internal mapping between the "propertyName" and value is up to you. If it's relatively small number of properties, you can use just a 2 dimensional array, or 2 arrays.
Related
Curios what are advantages/disadvantages between Factory with VB_PredeclaredId = True and "anonymous" instance in VBA. Each for its own scenarios or better stick to one of them and why? Any feedback or links where I can read more about it would be appreciated! Thank you!
Worksheet data staring A1
Name Manager Target
Person1 Manager1 97
Person2 Manager2 92
Person3 Manager3 95
Person4 Manager4 105
Person5 Manager5 108
Person6 Manager6 88
Factory - Class
'#Folder("VBAProject")
Option Explicit
'#PredeclaredId
Public Function getPerson(ByVal name As String, ByVal manager As String, ByVal target As Double) As Person
With New Person
.name = name
.manager = manager
.Targer = target
Set getPerson = .self
End With
End Function
Person - Class
Private pName As String
Private pManager As String
Private pTarger As Double
Public Property Get Targer() As Double
Targer = pTarger
End Property
Public Property Let Targer(ByVal value As Double)
pTarger = value
End Property
Public Property Get manager() As String
manager = pManager
End Property
Public Property Let manager(ByVal value As String)
pManager = value
End Property
Public Property Get name() As String
name = pName
End Property
Public Property Let name(ByVal value As String)
pName = value
End Property
Public Function toString() As String
toString = "Name: " & Me.name & ", Manager: " & Me.manager & ", Targer: " & Me.Targer
End Function
Public Function self() As Person
Set self = Me
End Function
Test - Module
Sub test()
Dim i As Long
For i = 2 To 6
With New Person
.name = Range("A" & i)
.manager = Range("b" & i)
.Targer = Range("c" & i)
Debug.Print .toString
End With
Debug.Print Factory.getPerson(name:=Range("A" & i), _
manager:=Range("B" & i), target:=Range("C" & i)).toString
'or shorter whithout feild names
Debug.Print Factory.getPerson(Range("A" & i), Range("B" & i), Range("C" & i)).toString
Next i
End Sub
TL;DR: It's apples and bananas, really. Anonymous objects are a language constructs; factories aren't defined in the language specs, they're more of a design pattern, and yes, there are different reasons to use each different approach - although IMO a "factory bag module" is a bad idea.
Anonymous Objects
Anonymous objects are awesome - you get to invoke members on an object that's held by a With block, without needing to add a local variable. That's With New, but it's also particularly useful with With CreateObject:
With CreateObject("Scripting.FileSystemObject")
'...
End With
They create objects, but such objects are (normally - a .Self getter can thwart that) confined to the containing With block; anonymous objects are useful for objects you need right here & now, and no longer need beyond that point. They are a language feature, basically.
Factory Class (or module)
We're leaving the realm of language features, and entering that of design patterns. Now we're talking about encapsulating the relatively complex creation of a given object, inside a class dedicated to that purpose.
The VB_PredeclaredId = True attribute (whether set by a #PredeclaredId Rubberduck annotation or not) makes that class work like any other standard module.
The problem with a Factory class that exposes a getPerson (or CreatePerson) method, is that you now have an excuse to extend this with some getAnotherThing (or CreateAnotherThing), and before you know it you're looking at a class/module that's able to create just about anything, whether these things are even remotely related or not.
Surely there's a better way.
Factory Method
What if a Person object knew how to create a new instance of the Person class?
Set p = Person.Create(1188513, "Mathieu")
This requires having the VB_PredeclaredId = True attribute on the Person class, and Person's default interface to expose a Create method to be invoked from that default instance.
The problem is that now anything that consumes a Person object is now seeing a confusing API that exposes Property Let members for ID and Name properties, and a Create function that's only meant to be used from the default instance.
This problem has a solution though: the only code that's "allowed" to work with Person, should be code that's responsible for invoking the Create factory method on that type. Everything else only ever sees an IPerson - an explicit interface that Person implements to define how the rest of the code shall interact with an object of that type... and IPerson does not need to expose any Property Let members, or Create method.
There's another problem that the pattern doesn't solve though - imagine IPerson is implemented by two or more classes (say, Employee and Manager): even if the entire project only ever sees IPerson, there's still some code in there that's calling Employee.Create or Manager.Create, and that code is inherently coupled with these specific implementations of the IPerson interface. It's a problem, because such coupling essentially negates the benefits of coding against interfaces in the first place.
Abstract Factory
In order to decouple the code that's creating IPerson objects from Employee and Manager classes, we could create an IPersonFactory interface that abstracts the factory method itself: now the code consuming the factory and create IPerson objects doesn't even know what concrete type of objects it's creating, and the decoupling is complete.
You probably don't need that level of decoupling unless you have everything covered with a thorough suite of unit tests, but it's useful to know it exists regardless.
So you would have EmployeeFactory and ManagerFactory classes, both implementing IPersonFactory and supplying an implementation for some Create function that returns an IPerson object.
...to be honest this is where the person/employee example kind of falls apart, because such a class is more of a simple DTO (Data Transfer Object - i.e. a bunch of public fields, or read/write properties) than anything that actually has responsibilities - so let's drop it.
We'll have a SqlConnectionFactory, an OracleConnectionFactory, and a MySqlConnectionFactory, all implementing some IDbConnectionFactory to yield some IDbConnection object that encapsulates, you'll have guessed, a database connection.
So we could have code that looks like this:
Public Sub DoSomething(ByVal factory As IDbConnectionFactory)
Dim pwd As String
pwd = InputBox("SA Password?") 'bad example: now we're coupled with the InputBox API!
Dim conn As IDbConnection
Set conn = factory.Create("server name", "sa", pwd)
'...
End Sub
That DoSomething method would be able to do its thing against an SQL Server, Oracle, or MySQL database server, and never needs to care which one it's actually working with.
Abstract Factory is useful when you're injecting dependencies (c.f. "dependency injection") that themselves have dependencies that you cannot resolve until last-minute, for example because you need some parameter that the user needs to provide through some UI (which is itself ideally also abstracted behind an interface, so that you can unit-test the method without popping that UI - although, Rubberduck's Fakes API does allow you to hijack an InputBox... imagine we're popping a UserForm instead).
I have a Visual Basic method called LogException which writes information into my Exceptions database in the event of a TRY..CATCH failure. That method has the following parameters:
methodLocation;
methodName;
exception;
When I invoke the method, I would use the following code:
_ex.LogException(
Me.GetType.Name.ToString,
MB.GetCurrentMethod.Name.ToString,
ex.Message.ToString)
Therefore, if I was invoking this code in a method called "Insert_Test" within a class called "Test", I would expect the first parameter to receive "Test", the second to receive "Insert_Test" and the third to receive the exact details from the exception that was thrown.
This all works fine as long as the "Test" class is the base class. If the "Test" class is a sub-class (for example called "BigTest"), the first two parameters would still be passed as "Test" and "Insert_Test". What I need to know is how to get the exact class tree, so that the first parameter in this scenario would come through as "BigTest.Test".
Ideally I'd like to be able to do this without having to hard-code any values into my code, so that the code can be re-used "as-is".
You could use a function like this:
Public Function GetFullType(ByVal type As Type) As String
Dim fullType As String = ""
While type IsNot GetType(Object)
If fullType = "" Then
fullType &= type.Name
Else
fullType = type.Name & "." & fullType
End If
type = type.BaseType
End While
Return fullType
End Function
And call it like this:
GetFullType(Me.GetType)
EDIT: It appears as though the OP is actually using nested classes, not inherited classes. In such case I found this answer which should be able to tweak into the code provided.
Code for nested classes:
Shared Function GetFullType(ByVal type As Type) As String
Dim fullType As String = ""
While type IsNot Nothing
If fullType = "" Then
fullType &= type.Name
Else
fullType = type.Name & "." & fullType
End If
type = type.DeclaringType
End While
Return fullType
End Function
If possible, don't invent it by yourself. For example, I can just guess that MB.GetCurrentMethod() will read the stacktrace to determine the method name (which is slow!).
You should check if the attributes CallerMemberName. CallerFilePath & CallerLineNumber fulfill your needs. They are filled in by the compiler and therefore won't hit any performance issues.
See:
https://blog.codeinside.eu/2013/11/03/caller-information-with-net-4-5-or-who-touched-the-function/
How to reference an object from Code(After of Page Setup, before References) ?
For example, given this class:
How can I use that object in Code pane, I tried this but it doesn't work:
Public Shared Function Test() As String
Test = "Hello " & m_Class1.SomeFunction()
End Function
I tried this too, not working too:
Public Shared Function Test() As String
Test = "Hello " & Code.m_Class1.SomeFunction()
End Function
Note, I don't have the assembly's source code, I cannot make my changes directly there
AFAIK you can't.
Whatever you write in the Code pane has to be static (Shared in vb), so you should wrap your "ClassLibrary2.Class1" in a static method and call it like ClassLibrary2.MyStaticClass.MyStaticMethod() (or in a new assembly, given that you can't recompile the original one)
Note that indeed you can call your instance m_Class1 instance from within the expression box of RS items like Code.m_Class1.SomeMethod()
I have a collection inside a class module. I'd like to restrict the object type that is "addable" to this collection, i.e. collection should only ever accept objects of one given type and nothing else.
Is there any way to enforce the type of objects added to a collection?
From what I can tell, there is no built-in way to do this. Is the solution then to make this collection private, and build wrapper functions for the methods usually accessible for Collections, i.e. Add, Remove, Item, and Count?
I kinda hate having to write 3 wrapper functions that add no functionality, just to be able to add some type enforcement to the Add method. But if that's the only way, then that's the only way.
There is no way to avoid wrapper functions. That's just inherent in the "specialization through containment/delegation" model that VBA uses.
You can build a "custom collection class", though. You can even make it iterable with For...Each, but that requires leaving the VBA IDE and editing source files directly.
First, see the "Creating Your Own Collection Classes" section of the old Visual Basic 6.0 Programmer's Guide:
http://msdn.microsoft.com/en-us/library/aa262340(v=VS.60).aspx
There is also an answer here on stackoverflow that describes the same thing:
vb6 equivalent to list<someclass>
However, those are written for VB6, not VBA. In VBA you can't do the "procedure attributes" part in the IDE. You have to export the class module as text and add it in with a text editor. Dick Kusleika's website Daily Dose of Excel (Dick is a regular stackoverflow contributer as you probably know) has a post from Rob van Gelder showing how to do this:
http://www.dailydoseofexcel.com/archives/2010/07/04/custom-collection-class/
In your case, going to all that trouble - each "custom collection" class needs its own module - might not be worth it. (If you only have one use for this and it is buried in another class, you might find that you don't want to expose all of the functionality of Collection anyway.)
This is what I did. I liked Rob van Gelder's example, as pointed to by #jtolle, but why should I be content with making a "custom collection class" that will only accept one specific object type (e.g. People), forever? As #jtolle points out, this is super annoying.
Instead, I generalized the idea and made a new class called UniformCollection that can contain any data type -- as long as all items are of the same type in any given instance of UniformCollection.
I added a private Variant that is a placeholder for the data type that a given instance of UniformCollection can contain.
Private mvarPrototype As Variant
After making an instance of UniformCollection and before using it, it must be initialized by specifying which data type it will contain.
Public Sub Initialize(Prototype As Variant)
If VarType(Prototype) = vbEmpty Or VarType(Prototype) = vbNull Then
Err.Raise Number:=ERR__CANT_INITIALIZE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__CANT_INITIALIZE) & _
TypeName(Prototype)
End If
' Clear anything already in collection.
Set mUniformCollection = New Collection
If VarType(Prototype) = vbObject Or VarType(Prototype) = vbDataObject Then
' It's an object. Need Set.
Set mvarPrototype = Prototype
Else
' It's not an object.
mvarPrototype = Prototype
End If
' Collection will now accept only items of same type as Prototype.
End Sub
The Add method will then only accept new items that are of the same type as Prototype (be it an object or a primitive variable... haven't tested with UDTs yet).
Public Sub Add(NewItem As Variant)
If VarType(mvarPrototype) = vbEmpty Then
Err.Raise Number:=ERR__NOT_INITIALIZED, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__NOT_INITIALIZED)
ElseIf Not TypeName(NewItem) = TypeName(mvarPrototype) Then
Err.Raise Number:=ERR__INVALID_TYPE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__INVALID_TYPE) & _
TypeName(mvarPrototype) & "."
Else
' Object is of correct type. Accept it.
' Do nothing.
End If
mUniformCollection.Add NewItem
End Sub
The rest is pretty much the same as in the example (plus some error handling). Too bad RvG didn't go the whole way! Even more too bad that Microsoft didn't include this kind of thing as a built-in feature...
I did almost the same code of Jean-François Corbett, but I adapted because for some reason wasn't working.
Option Explicit
Public pParametro As String
Private pColecao As New Collection
Public Sub Inicializar(ByVal parametro As String)
pParametro = parametro
End Sub
Public Sub Add(NewItem As Object)
If TypeName(NewItem) <> pParametro Then
MsgBox "Classe do objeto não é compatível à coleção"
Else
pColecao.Add NewItem
End If
End Sub
Public Property Get Count() As Long
Count = pColecao.Count
End Property
Public Property Get Item(NameOrNumber As Variant) As Variant
Set Item = pColecao(NameOrNumber)
End Property
Sub Remove(NameOrNumber As Variant)
pColecao.Remove NameOrNumber
End Sub
Then, when i want to create an instance from CCollection I make like the code bellow:
Set pFornecedores = New CCollection
pFornecedores.Inicializar ("CEmpresa")
Where CEmpresa is the class type from the object I want
Yes. The solution is to make your collection private and then make public wrapper functions to add, remove, getitem and count etc.
It may seem like hassle to write the additional code but it is a more robust solution to encapsulate the collection like this.
I just discovered that the Me keyword cannot access private procedures even when they are inside its own class model.
Take the following code in Class1:
Private Sub Message()
Debug.Print "Some private procedure."
End Sub
Public Sub DoSomething()
Me.Message
End Sub
This code instantiates an instance of the class:
Sub TestClass()
Dim objClass As New Class1
objClass.DoSomething
End Sub
Me.Message throws compile error "Method or data member not found."
If I change Private Sub Message() to Public the procedure works fine. I can also remove the Me keyword from the DoSomething procedure, but I was under the impression that the idea behind the Me keyword is to ensure that multiple instances of Class1 are properly encapsulated.
Why can't the VBA Me keyword access procedures in its own module when they are private? Is it safe to omit the Me keyword and do something like this in a class?
Private Sub Message()
Debug.Print "Some private procedure."
End Sub
Public Sub DoSomething()
Message
End Sub
Thanks!
Update: Thanks for the tips on proper syntax, my code is working. I am still looking for an explanation of why Me can reference private procedures in an instance of it's own module. I couldn't find any good documentation.
Any guess as to why it was designed that way would be pure supposition without talking to the designers. But my own guess is this, the Me keyword returns a reference to the object the code is currently executing in. I would guess rather than create a special case for Me, they found it easier to continue to obey rules of scope for an object. Which is to say object.method can only work on public or friend methods. So Me, is exactly what it says, an instance of the currently executing object. And since VBA/VB6 doesn't have shared methods, it doesn't really matter if you prefix with Me or not.
But if it makes you feel any better, I find it incredibly obnoxious too.
You do not need the Me keyword to call inside own class.
Me is this class object instance. So no one can directly call private subs or functions or access private variables except this class public functions or subs.
Public Function Fight() As String
'performs a round of attacks i.e. each character from both sides performs an attack
'returns a scripted version of the outcomes of the round
'check if buccaneers are all dead
If mBuccaneers.aliveCount > 0 Then
'check if any hostiles are alive
If mHostiles.aliveCount > 0 Then
'check we have some buccaneers
If mBuccaneers.count = 0 Then
Fight = "There are no buccaneers. Load or create some buccaneers"
Else
If mHostiles.count = 0 Then
'can't fight
Fight = "There are no hostiles to fight. Generate some hostiles"
Else
mScript = ""
Call GroupAttack(mBuccaneers, mHostiles)
Call GroupAttack(mHostiles, mBuccaneers)
Fight = mScript
End If
End If
Else 'hostiles are all dead
Fight = "Hostiles are all dead. Generate a new set of hostiles"
End If
Else
Fight = "Buccaneers are all dead :(. Suggest building or loading a new buccaneer group"
End If
End Function
Uses the private class method GroupAttack by using the Call statement
Private Sub GroupAttack(attackersGroup As clsGroup, defendersGroup As clsGroup)
'implements the attack of one group on another
Dim victimNo As Integer
Dim randomNumber As Integer
Dim attacker As clsCharacter
Dim damage As Integer
Dim defender As clsCharacter
Randomize
For Each attacker In attackersGroup.members
'check if attacker is still alive
If attacker.health > 0 Then
'check if any defenders are still alive because there's no point attacking dead defenders
If defendersGroup.aliveCount > 0 Then
'do some damage on a defender
If defendersGroup.count > 0 Then
'choose a random hostile
victimNo = Int(((Rnd() * defendersGroup.aliveCount) + 1))
'find an alive victim
memberid = 0
j = 0
Do While j < victimNo
memberid = memberid + 1
If defendersGroup.members(memberid).health > 0 Then
j = j + 1
End If
Loop
'reset our victimno to the live victim
victimNo = memberid
damage = defendersGroup.character(victimNo).attack(attacker.strength)
If damage <> 0 Then 'attacker hit
mScript = mScript & attacker.name & " hits " & _
defendersGroup.character(victimNo).name & " for " & damage & " damage"
If defendersGroup.character(victimNo).health = 0 Then
mScript = mScript & " and kills " & defendersGroup.character(victimNo).name
End If
mScript = mScript & vbCrLf
Else 'attacker missed
mScript = mScript & attacker.name & " missed " & defendersGroup.character(victimNo).name & vbCrLf
End If
End If
End If
End If
Next attacker
End Sub
Thats all you need to do ,works like a charm
In COM, there's a difference between the types of object instances and the types of object variables. In particular, the types of object variables behave as interface types. Every type implements at least one interface (itself), but types may implement other interfaces as well. Such ability is used to fake inheritance.
In some frameworks, if class Foo has a private member Bar, then any non-null variable of type Foo will hold a reference to some class object which contains that member. The member may not be accessible to any outside code, but it will exist, and can thus be accessed from anywhere within the code for Foo.
Because COM class-variable types behave like interfaces rather than inheritable class types, however, there's no guarantee that a variable of type Foo will refer to an object which has any of Foo's non-public members. While a compiler could know that Me will always refer to the present object, which will be of actual type Foo, the fact that the only object upon which a private member of Foo could be accessed is Me means that there's no real reason for the compiler to support dot-based dereferencing of private members.