I have this Excel VBA code, and I just dont understand how the control flow works. It uses some kind of clever trick. Can someone help me fill in the details?
Basically, the code creates a collection, it creates some objects from a class "cls_FX", and fills the collection with the objects. Then there is a test which I dont understand. It looks like this. And nowhere has the string "Test" been added to the collection, as far as I can see. Can someone explain the code? What is the strategy of the strange test Err.Number=0? Why test with 0?
cls_FX:
Option Explicit
Public str_fx As String
Public col_Years As New Collection
Public str_Test As String
Code:
var_FX_Array = Array("USD", "GDP", "EUR")
For j = 0 To 3
Dim ci_cls_FX As New cls_FX
ci_cls_FX.str_fx = var_FX_Array(j)
col_FX.Add ci_cls_FX, ci_cls_FX.str_fx
Set ci_cls_FX = Nothing
Next j
str_fx = "USD"
Err.Clear
col_FX(str_fx).str_Test = "Test"
If Err.Number = 0 Then
str_Year = .Offset(i, 2)
....
Related
I am attempting to modify the following function to have variable strings that join together to run the changed final function. I know this isn't right, but I have been googling for a couple of days now and tried a few different methods to no avail. I'm sure I've been googling the wrong information to find the right way to do this, any help would be greatly appreciated! Please note the comment section at the bottom is what I am trying to achieve in the final function.
Update Reason I'm asking:
There are multiple pages(about 10) that I want multiple photos(around 173) to load for and figured it would be a good way to condense the code. Do you know a way to work around this without having to have the 1000 lines of code?
Option Explicit
Dim Form As String
Dim Form1 As String
Dim Form2 As String
Dim Form3 As Object
Sub YouthLoadGraphics()
Form = "Youth"
Form1 = ".Picture = LoadPicture("
Form2 = ")"
Form3 = Form + Form1 + ("C:\temp\forms\Leaving-Youth-Readiness-Assessment-Pens_1.bmp") + Form2
Run Form3
' Youth.Picture = LoadPicture("C:\temp\forms\Leaving-Youth-Readiness-Assessment-Pens_1.bmp")
' Youth.PictureSizeMode = (3)
End Sub
I've been wrestling with this problem for a while. My problem is that I have a bunch of JSON data and I want to represent it as objects.
Arrays are problematic.
I create a class module such as FancyCat with a public Name as String for its name.
Then I can set this with
Dim MyFancyCat as FancyCat
Set MyFancyCat = new FancyCat
FancyCat.Name = JSONData("Name")
I've typed that from memory but I think it's correct. Anyhoo, it works fine.
The problem is that a fancy cat has several pairs of socks. The number of socks is variable.
In vba you cannot for some reason have a public array. So this code is illegal:
public Socks() as FancySock 'Illegal
Looking on SO I found two solutions, one, to make it private and use a property to access it, and the other, to declare it as Variant and then stick an array into it later.
My approach to populating this array, is to examine the JSON array to get the Count, and then to ReDim the array to match and then populate it.
The problem is my ReDim statement refuses to work.
It seems I cannot redim a property, I get an error. And I also get an error trying to redim the public variant field. My ReDim works OK if I declare a local array and redim it, so potentially I could do that and then assign it to the property... but it just seems bizarre that I can't redim it directly.
Any idea why it's not working?
With the Variant approach above my code is:
ReDim MyFancyCat.Socks(socksLength) As FancySocks
And in the FancyCat class module:
public Socks As Variant
I get Method or Data Member Not Found.
The error for the other approach was different but I rejigged all my code to try the second approach so I am not sure what it was.
Edit: I'm gonna explain what I am trying to do a bit more clearly. I have some JSON data coming in, and I want to store it as an object hierarchy.
In C# I would do this (pseudo code without linq shortcuts):
var myData = ReadJsonData(); // Produces a kind of dictionary
var myFancyCat = new FancyCat();
myFancyCat.Name = myData["Name"];
myFancyCat.Age = myData["Age"];
myFancyCat.Socks = new List<FancySock>();
foreach (var sock in myData["Socks"])
{
myFancyCat.Socks.Add(sock);
}
In excel I want to do the same thing.
So I make a class module for FancyCat and FancySock and give FancyCat public members for Name, Age etc but then I also want an array of socks that my cat owns. I wanted to do this with strongly typed references, e.g. my c# code above I can do:
myFancyCat.Socks[0].Colour // Intellisense works, shows colour as a property
However it seems in excel you can't have publicly declared arrays. So you can get around this according to the comments by declaring it as variant and then sticking an array in anyway, but you would lose the intellisense. Or you can use a get/let property which kinda works but is more fiddly as it seems you can't actually expose an array using a get/let you have to have it take an index and expose elements individually.
So at this point I am thinking forget the strongly typed it's not happening, perhaps use a collection?
The FancySock class may have further nested arrays within it. I've read that there's no ByRef for arrays (at least, not completely - I think you can get an array ByRef but not set one?). I am not sure if that would create problems with trying to set it.
But ultimately, I just want to end up with my JSON data represented easily in an OO way, so that in my excel ultimately I can just do
myFancyCat.Name or myFancyCat.Socks.Count or myFancyCat.Socks(1).Colour etc
It seems much harder than it looks to simply deserialise JSON into 'objects' in vba.
Please, try the next way:
Insert a class module, name it FancyCat and copy the next code:
Option Explicit
Private arrL As Object
Public myName As String, myAge As Long
Public Sub Class_Initialize()
Set arrL = CreateObject("System.Collections.ArrayList")
End Sub
Public Property Let Name(strName As String)
myName = strName
End Property
Public Property Let Age(lngAge As String)
myAge = lngAge
End Property
Public Property Let SocksAdd(sMember)
arrL.Add sMember
End Property
Public Property Get Socks() As Variant
Socks = arrL.toarray()
End Property
Use it in the next testing Sub:
Sub testClassDictListArray()
Dim myFancyCat As New FancyCat, myData As Object
Dim arrSocks, sock
Set myData = CreateObject("Scripting.Dictionary") 'this should be the dictionary returned by ParseJSON
myData.Add "Name", "John Doe": myData.Add "Age", 35
myData.Add "Socks", Array("Blue", "White", "Red", "Green", "Yellow")
myFancyCat.Name = myData("Name")
myFancyCat.Age = myData("Age")
For Each sock In myData("Socks")
myFancyCat.SocksAdd = sock
Next sock
arrSocks = myFancyCat.Socks
Debug.Print Join(arrSocks, "|")
End Sub
I am not sure I perfectly understand the scenario you try putting in discussion...
If you want to benefit of instellisense suggestions, I will tell you what references to be added. Even, I will send two pieces of code to automatically add the necessary references (I mean, Scripting.Dictionary and ArrayList`).
Please, test it and send some feedback.
In your class:
Private m_Name As String
Private m_Socks() As String
Public Property Let Name(Name As String)
m_Name = Name
End Property
Public Property Get Name() As String
Name = m_Name
End Property
Public Sub SetSize(Quantity As Long)
ReDim m_Socks(1 To Quantity)
End Sub
Public Property Let Socks(Index As Long, Sock As String)
m_Socks(Index) = Sock
End Property
Public Property Get Socks(Index As Long) As String
Socks = m_Socks(Index)
End Property
In a regular module:
Sub UseFancyCat()
Dim MyFancyCat As FancyCat
Set MyFancyCat = New FancyCat
MyFancyCat.Name = "Fancy Name"
MyFancyCat.SetSize 2
MyFancyCat.Socks(1) = "Sock1"
MyFancyCat.Socks(2) = "Sock2"
Debug.Print MyFancyCat.Name
Debug.Print MyFancyCat.Socks(1)
Debug.Print MyFancyCat.Socks(2)
End Sub
Let's say that I have an object...
Class test
Public a
Public b
End class
And in my code I would like to instantiate it without knowing a predetermined variable name to store the new instance.
Is this possible? How would I then dim the random variable?
I want to be able to do this because I don't want my user input to be stored or saved in the same variable with other data conflict. Like say for example I am storing stats for an athlete and I ask the athlete's name. If every time the user enters a different name, I don't want to have this information in the same object instance. Could I create an object for the athlete and reference this object inside another object?
I have a snippet of code:
Function addStats
dim pAtt, pComp, pInt, pTds, pYds, endNum, pName
Wscript.StdOut.WriteLine "What is your quarterback's name"
pName = Wscript.StdIn.ReadLine
Wscript.StdOut.WriteLine "How many attempts: "
pAtt = Wscript.StdIn.ReadLine
'chkNum(pAtt)
Wscript.StdOut.WriteLine "How many completions: "
pComp = Wscript.StdIn.ReadLine
'chkNum(pComp)
Wscript.StdOut.WriteLine "How many yards: "
pYds = Wscript.StdIn.ReadLine
'chkNum = pYds
Wscript.StdOut.WriteLine "How many touchdowns: "
pTds = Wscript.StdIn.ReadLine
'chkNum = pTds
Wscript.StdOut.WriteLine "How many interceptions: "
pInt = Wscript.StdIn.ReadLine
'chkNum = pInt
endNum = UBound(newStats) + 1
redim preserve newStats(endNum)
'---- vvvv ----
set newStats(endNum) = new QB
'---- ^^^^ ----
newStats(endNum).att = pAtt
newStats(endNum).comp = pComp
newStats(endNum).yds = pYds
newStats(endNum).tds = pTds
newStats(endNum).ints = pInt
newStats(endNum).qbname = pName
Wscript.StdOut.WriteLine "Stats Added"
writeBuffer()
end Function
The object is:
class QB
dim att, comp, yds, tds, ints, qbname
public property let qbAtt(n)
att = n
end property
public property let qbComp(n)
comp = n
end property
public property let qbYds(n)
yds = n
end property
public property let qbTds(n)
tds = n
end property
public property let qbInt(n)
ints = n
end property
public property let qName(n)
qbname = n
end property
public property get qbAtt
qbAtt = att
end property
public property get qbComp
qbComp = comp
end property
public property get qbYds
qbYds = yds
end property
public property get qbTds
qbTds = tds
end property
public property get qbInt
qbInt = ints
end property
public property get qName
qName = qbname
end property
end class
The highlighted statement is me instantiating the object with a global variable - newStat(). My belief is that I would have to either create a class for just the quarterback and somehow reference this into a variable array that is determined on the quarterback's name or create a function that takes the user prompt variable that asks the quarterback's name and set it as an array that instantiates the QB class.
I think you're confusing classes, objects, and variables. Classes are basically templates that describe which properties and behavior entities of a particular category have. Objects are instances of a class. Variables are identifiers that refer to objects (or data of primitive types).
Take lockers for example. Lockers usually have a color, they can be opened and closed, and they may contain a number of items. Thus a general description of lockers (a class "Locker") might look like this:
Class Locker
Public color
Public closed
Public content
Sub Class_Initialize
closed = True
content = Array()
End Sub
Sub Open
closed = False
End Sub
Sub Close
closed = True
End Sub
End Class
The constructor (Class_Initialize) is a special method that is only called when an object (an instance of a class) is created. It sets the initial state of the object. Kind of like when a locker is built in the factory.
A particular locker (object) might be green and contain a hat and a magazine, while another locker (object) might be blue and contain a book and a jacket.
Set locker_A = New Locker
locker_A.color = "green"
locker_A.content = Array("hat", "magazine")
Set locker_B = New Locker
locker_B.color = "blue"
locker_B.content = Array("book", "jacket")
To be able to actually work with objects (or other data) in a program you need variables. These identifiers (locker_A and locker_B in the example above) allow you to refer to particular objects in your program code in order to access their properties and methods.
When you run a statement
Set newStats(2) = New QB
it creates a new instance of the class QB (a new object) and places a reference to that object in the third slot of the array newStats. Afterwards you can use newStats(2) to refer to that object in your program or script. For instance:
newStats(2).yds = 42 'yards
If you have a number of QB object and store each of them in a different array slot there shouldn't be any conflict between them, as long as you don't replace a reference in one slot with another one.
Set newStats(2) = newStats(5) '<-- don't do this
If you wanted to access objects from the list by a particular property (for instance the name) instead of by index you'd use a Dictionary instead of an array:
Set newStats = CreateObject("Scripting.Dictionary")
newStats.CompareMode = vbTextCompare 'make lookups case-insensitive
...
Set player = New QB
player.att = pAtt
...
player.qbname = pName
newStats.Add pName, player
Then you could access a particular player like this:
name = "Charley Johnson"
WScript.Echo newStats(name).yds
I am trying to create a class (named ClassSection) that contains a collection (named DefectCollection). It needs a function to add items to that collection but I'm having trouble making it work. I get Error 91 "Object variable or with block variable not set."
I have looked at the other answers on here, which is what got me this far, but I don't understand what I'm missing.
Here is the class module code:
Public DefectCollection As Collection
Private Sub Class_Initialise()
Set DefectCollection = New Collection
End Sub
Public Function AddDefect(ByRef defect As CDefect)
DefectCollection.Add defect [<---- error 91]
End Function
And here is the code that calls the function: ('defect' is another class, which works fine - I want each 'ClassSection' to be able to hold an unlimited number of 'defects')
Dim SC As Collection
Dim section As ClassSection
Set SC = New Collection
Dim SurveyLength As Double
For Each defect In DC
SurveyLength = WorksheetFunction.Max(SurveyLength, defect.Pos, defect.EndPos)
Next defect
SurveyLength = Int(SurveyLength)
For i = 0 To numSurveys
For j = 0 To SurveyLength
Set section = New ClassSection
section.ID = CStr(j & "-" & dates(i))
SC.Add Item:=section, Key:=section.ID
Next j
Next i
Dim meterage As Double
For Each defect In DC
meterage = Int(defect.Pos)
Set section = SC.Item(meterage & "-" & defect.SurveyDate)
section.AddDefect defect
Next defect
Thanks!
You get the error because the DefectCollection is Nothing. This is due to the fact that you mispelled the initalization method:
Private Sub Class_Initialise() '<-- it's with "Z", not "S"
Hence, the initialization of the class is never called, the object remain Nothing by default and the method fails when trying to add an object to Nothing
I have code like this:
Dim MyACL As Variant
Dim Person As List
Redim MyACL(0)
Person("Detail1") = "Something1"
.
.
.
Person(Detailx") = "Somethingx"
ForAll name in names
ReDim Preserve MyAcl(Ubound(MyACL)+1)
Person("Name") = name
MyACL = ArrayAppend(MyACL,Person)
End ForAll
It throws error "Type Mismatch". Do you know, how to create an array of lists? Thank you.
This is a typical example of when you want to use a class instead, and create an array of that class. That class, in turn can contain a list (as well as other things). Can be very powerful!
Updated:
The benefit of using a class is that you can add business logic in the class, and it is very easy to extend it with more functionality later. Below is an example, based on the question above, but with additional functionality.
Class PersonObject
Public PersonAttribute List As String
Public NABdoc As NotesDocument
Public PersonName As String
Public Sub New(personname As String)
Dim nab as New NotesDatabase("Server/Domain","names.nsf")
Dim view as NotesView
'*** Get person document from Domino directory
Set view = nab.GetView("PeopleByFirstName")
Set me.NABdoc = view.GetDocumentByKey(personname)
'*** Set person name in object
me.PersonName = personname
'*** Set some values from person doc
me.PersonAttribute("Email") = GetValue("InternetAddress")
me.PersonAttribute("Phone") = GetValue("OfficePhone")
End Sub
Public Function GetValue(fieldname as String) as String
GetValue = me.NABdoc.GetItemValue(fieldname)(0)
End Function
Public Sub AddAttribute(attributename as String, value as string)
me.PersonAttribute(attributename) = value
End Sub
End Class
You can now very easily build you a list, using this class (and assuming that names is a list of unique names):
Dim person List As PersonObject
Dim personname As String
ForAll n in names
'*** Create person object based on name
person(n) = New PersonObject(n)
'*** Store additional info about this person
person.AddAttribute("Age","35")
End ForAll
Hopefully this gives you an idea of what you can do with classes.
You can also take a look at the following two blog entries about the basics of object oriented Lotusscript:
http://blog.texasswede.com/object-oriented-lotusscript-for-beginners-part-1/
http://blog.texasswede.com/object-oriented-lotusscript-for-beginners-part-2/
If you explicitely declare a variable as Array (as you do in your Redim Statement), then it can not be "reassigned" using arrayappend.
And it is NOT necessary to do it that way. just replace the line MyACL = ArrayAppend(MyACL,Person) with MyACL(Ubound(MyACL)) = Person
Take care: With that example code you will never fill MyACL(0) as the first Element filled is MyACL(1)
To begin filling the array with element 0 the code needs to be changed like this:
Dim max As Integer
max = 0
ForAll thisName In names
ReDim Preserve MyAcl(max)
Person("Name") = thisName
MyACL(max) = Person
max = max + 1
End ForAll
BUT: I don't know, if this is a good idea, as you can not access the "Detail1- Property" of Person directly.
Something like
detail = MyACL(1)("Detail1")
is not possible. You always have to have a temporary variable like this:
person = MyACL(1)
detail = person("Detail1")