I'll try to explain it to the best of my ability with my limited knowledge...
I have a square matrix, same amount of rows and columns, i need to loop thru it and store the information of each cell in a certain way, i've done it previously with arrays and got everything working, but i'm trying to improve on my coding skills, and i've known for a while that objects are the way to go usually... this is my second attempt after quite a while of just not trying due to being defeated...
The thing is... I have 2 objects, one named Proveedor, this guy will have 3 properties:
Unidad Decisoria_Prov: a string
Responsable_Prov: a string
Requerimientos: an array of Vinculo objects
Private pedidos_() As Vinculo
Private ud_P As String
Private responsable_P As String
Option Explicit
Public Property Let Requerimientos(index As Integer, str As Vinculo)
If index > UBound(pedidos_) Then ReDim Preserve pedidos_(index)
If pedidos_(index) Is Nothing Then
Set pedidos_(index) = New Vinculo
End If
Set pedidos_(index) = str
End Property
Public Property Get Requerimientos(index As Integer) As Vinculo
If index > UBound(pedidos_) Then Requerimientos = "El indice del get esta fuera de rango": Exit Property
Requerimientos = pedidos_(index)
End Property
Public Property Let Unidad_Decisoria_Prov(str As String)
ud_P = str
End Property
Public Property Get Unidad_Decisoria_Prov() As String
Unidad_Decisoria_Prov = ud_P
End Property
Public Property Let Responsable_Prov(str As String)
responsable_P = str
End Property
Public Property Get Responsable_Prov() As String
Responsable_Prov = responsable_P
End Property
Private Sub class_initialize()
ReDim pedidos_(0)
End Sub
The other object is named Vinculo, this one has 3 properties:
Unidad Decisoria_C: a string
Responsable_C: a string
Requerimiento: a string
Private ud_ As String
Private responsable_ As String
Private requerimiento_ As String
Option Explicit
Public Property Let Unidad_Decisoria_C(str As String)
ud_ = str
End Property
Public Property Get Unidad_Decisoria_C() As String
Unidad_Decisoria_C = ud_
End Property
Public Property Let Responsable_C(str As String)
responsable_ = str
End Property
Public Property Get Responsable_C() As String
Responsable_C = responsable_
End Property
Public Property Let Requerimiento(str As String)
requerimiento_ = str
End Property
Public Property Get Requerimiento() As String
Requerimiento = requerimiento_
End Property
Once I have the objects working as intended i'll loop thru the matrix and do what i need to with the data, but before wasting time on that, i'm trying to test it with the following code:
Sub testing_2_objetosjuntos()
Dim mi_Prov As Proveedor
Dim un_vin As Vinculo
Set mi_Prov = New Proveedor
Set un_vin = New Vinculo
mi_Prov.Unidad_Decisoria_Prov = "tarea del proveedor 1"
mi_Prov.Responsable_Prov = "responsable de la tarea del proveedor 1"
un_vin.Unidad_Decisoria_C = "tarea del cliente 1"
un_vin.Responsable_C = "responsable de la tarea 1 del cliente"
un_vin.Requerimiento = "hace tal cosa"
mi_Prov.Requerimientos(0) = un_vin
Debug.Print mi_Prov.Requerimientos(0).Requerimiento
Debug.Print mi_Prov.Requerimientos(0).Responsable_C
End Sub
Up to the debug.print command everything works fine as far as i can tell...
watches up until the first debug.print line
however when i try to access the properties of the vinculo object stored in the first index of the Proveedor object i get a beautiful
Run-time error 91:
Object variable or With block variable not set
The actual line that gives me that error is the get property of Requerimientos in the Proveedor class.
This is probably a silly question but not only i can't understand why it breaks, i'm unable to apparently ask the question properly in google to not have to bother you guys...
I expected to read the string stored in the vinculo.Requerimiento property which is in the first index of the array of Requerimientos property of the Proveedor object.
The reason for your problem is easy to fix. As Vinculo is an object, you need to use the Set keyword in the Requerimientos-getter:
Public Property Get Requerimientos(index As Integer) As Vinculo
Set Requerimientos = pedidos_(index)
End Property
However, your error handling will fail: If index > UBound(pedidos_), you are assigning a String as return value which is not allowed (remember, your function returns an object of type Vinculo, not a String).
You should raise an error instead
Public Property Get Requerimientos(index As Integer) As Vinculo
Err.Raise vbObjectError + 1000, "El indice del get esta fuera de rango", "Sorry..."
Set Requerimientos = pedidos_(index)
End Property
And a hint: You had problems to identify the error of your test routine because the VBA runtime didn't halt on the line of error. This is because the default setting of the VBA debugger is "Break on unhandled errors" which doesn't include to break within a class module (hint: Userforms are also classes). Change this setting to "Break in class modules" to see the exact line where the error occurs.
Tools->Options->General
Related
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
Sub Process_Globals
'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
Type DBResult (Tag As Object, **Columns As Map**, Rows As List)
Type DBCommand (Name As String, Parameters() As Object)
Private const rdcLink As String = "http://192.168.8.100:17178/rdc"
End Sub
This is the methods for process globals . Here columns As Map is initialized .
However the line bolded in the below code gives an error as , "Object converted to String. This is probably a programming mistake. (warning #7)"
Sub GetRecord
Dim req As DBRequestManager = CreateRequest
Dim cmd As DBCommand = CreateCommand("selectAllNames", Null)
Wait For (req.ExecuteQuery(cmd, 0, Null)) JobDone(j As HttpJob)
If j.Success Then
req.HandleJobAsync(j, "req")
Wait For (req) req_Result(res As DBResult)
'work with result
'req.PrintTable(res)
***Log(res.Columns)***
ListViewListTable.Clear
For Each row() As Object In res.Rows
Dim oBitMap As Bitmap
Dim buffer() As Byte
buffer = row(res.Columns.Get("gambar"))
oBitMap = req.BytesToImage(buffer)
ListViewListTable.AddTwoLinesAndBitmap(row(1), "See more...", oBitMap)
Next
Else
Log("ERROR: " & j.ErrorMessage)
End If
j.Release
End Sub
So what should I do to remove the error?
If columns is a map? (which it looks to be?).
Then to display the columns you can use this:
For Each MyKey As String in res.Columns.Keys
log("Key name = " & MyKey)
log("Key value = " & res.Columns.Get(MyKey))
Next
is there way how to make class working similar to Arrays?
Let's say, I have Class (e.g. Workers) where main property is array of the Workers, nothing else.
Then I'm filling the class as follows
Dim wks as new Workers
wks.add("Worker1")
wks.add("Worker2")
wks.add("Worker3")
Then in Workers Class module:
Private Workers as Variant
Public Function add(ByVal val As Variant) As Long
ReDim Preserve Workers(LBound(Workers) To UBound(Workers) + 1)
Workers(UBound(Workers)) = val
add = UBound(Workers) - LBound(Workers) +1
End Function
Workers representation -> {"Worker1", "Worker2", "Worker3"}
Then I want to access Worker by its index. I know, how to access it by e.g wks.getWorker(1) but what I want to do, is to access it directly by wks(1) which should return "Worker 1". Example above looks, that usual Array or Collection can be used, but I have many internal methods done, only what I'm missing is to access Workers property to read/write directly by its index number.
Is it possible?
Edit
After transfer to Collections, Class looks like:
Option Explicit
Private Workers As Collection
Private Sub Class_Initialize()
Set Workers = New Collection
End Sub
Public Function add(ByVal val As Variant) As Long
Workers.add val
End Function
Public Property Get Item(Index As Integer) As Variant
Item = Workers(Index)
End Property
Public Property Set Item(Index As Integer, Value As Variant)
Workers.Remove Index
Workers.add Value, Before:=Index
End Property
with hidden attributes Attribute Item.VB_UserMemId = 0 at Getter and Setter.
Getting works fine:
Dim wks As New Workers
wks.add "Worker1"
wks.add "Worker2"
wks.add "Worker3"
Debug.Print wks(2) ' <-- OK here
'wks(2) = "Second Worker" ' <-- By debugging this go to Getter not Setter and after Getter is done, it allerts with Runtime error '424': Object required
Set wks(2) = "Second Worker" ' <-- This alert immediately Compile error: Object required on "Second Worker" string
Debug.Print wks(2)
Prints "Worker2" into console, thanks for this, but still I'm not able to set a new value to the required Index of the Workers Collection.
You could use a default member in VBA. Though you can't make the default memeber directly through VBA editor, but you can use any text editor.
Export your class from VBA editor, i.e. File->Export File
Open your exported class in Notepad (or any text editor)
Add this attribute line on your method or property you want to make it default. Attribute Item.VB_UserMemId = 0
You can for example make getWorker default member as.
Public Function GetWorker(Index As Integer) As Worker
Attribute Item.VB_UserMemId = 0
GetWorker = Workers(Index)
End Function
you can then use it like.
Set wk = wks(1)
Here is some detail about default members
http://www.cpearson.com/excel/DefaultMember.aspx
Edits
An example to make Getter/Setter as default member
Public Property Get Item(Index as Integer) as Worker
Attribute Item.VB_UserMemId = 0
Set Item = Workers(Index)
End Property
Public Property Set Item(Index as Integer, Value as Worker)
Attribute Item.VB_UserMemId = 0
Set Workers(Index) = Value
End Property
I can't use my Property Get in my main program.
I have already got this problem with the constructor of my class, I put Set before every property.
Public ID As Integer
Public numberOfError As Integer
Public error1 As Erreur
Public error2 As Erreur
Public error3 As Erreur
Public error4 As Erreur
Public Sub ajouterDTC(bid As Integer, Optional bnumberOfError As Integer, Optional berror1 As Erreur, Optional berror2 As Erreur, Optional berror3 As Erreur, Optional berror4 As Erreur)
With Me
.ID = bid
.numberOfError = bnumberOfError
Set .error1 = berror1
Set .error2 = berror2
Set .error3 = berror3
Set .error4 = berror4
End With
End Sub
'error1 properties
Public Property Get getError1() As Erreur
getError1 = error1
End Property
Public Property Let letError1(berror1 As Erreur)
error1 = berror1
End Property
"Erreur is still an object, so you need to Set it. Set getError1 = error1."
by Vincent G
I have been asked to modify an Excel sheet with some arcaic programming. I have decided to rewrite it rather then modify all of the many GOTO statments and static arrays. My background is in C# so it has been a bit of a challenge (note: I am sure the naming convention is bad, I am used to being able to use underscore to define private variables)
I am having trouble inializing an attribute of the type dictionary within a class that I have in a VBA application.
The shortened version of the class looks like this
Private pTerminalCode As String
Private pTerminalName As String
...... other attributes
Private pPayRoll As Dictionary
'Propeties
Public Property Get terminalCode() As String
terminalCode = pTerminalCode
End Property
Public Property Let terminalCode(Value As String)
pTerminalCode = Value
End Property
....... more properties
Public Property Get headCount() As Dictionary
headCount = pHeadCount
End Property
Public Property Let headCount(Value As Dictionary)
pHeadCount = Value
End Property
When I try to use the following I get the error "Argument not optional" within the Get property of the headCount() attribute.
Private Function PopulateTerminal()
Dim terminal As clsTerminal
Set terminal = New clsTerminal
terminal.terminalCode = "Wil"
terminal.headCount.Add "Company", 100
End Function
I assume somewhere I need to inialize the dictionary (i.e. = New Dictionary) however I am strugling with where to place it. In C# I do this in the constructor without issue, not sure what to do here.
Thanks
You can do it in the constructor of the VBA class, like so:-
Public Sub Class_Initialize()
Set myDictionary = New Dictionary
End Sub
Don't forget to always use the Set keyword when assigning an object reference, e.g.:-
Public Property Get Foo() As Dictionary
Set Foo = myDictionary
End Sub