Accessing Items in a Collection from a Class - vba

-EDIT Fixed
I was missing one thing and doing one thing wrong. First I was missing a function to access the collection by index. And I should of been using a for Loop instead of a for each loop in my module code
I forgot to add this to the collection class
Public Function GetPayRecords(ByVal index As Variant) As PayRecords
Set GetPayRecords = pObjCol.item(index)
End Function
and replaced
For Each vItem In .GetPayRecords
....code to do stuff
Next vItem
with this in the module
Dim x As Integer
For x = 1 To .Count
Debug.Print .GetPayRecords(x).PY_PayRecord.CEOCompanyID
Debug.Print .GetPayRecords(x).PY_PayRecord.OrigBankID
Next x
I'm writing a program that has 8 Classes. Each class represents a specific record type.
I have an overall Class that contains those 8 classes which is for simplicity when coding in the Module. I only have to declare one class which gives me access to all 8 classes. I have a collection which contains all the records types. Once all the logic of loading the individual records is complete they get added to the collection. This all works perfectly and I can see all the records in the collection. The final step, which happens to be where i'm having the problem, I need to extract each item within the collection by record type and write it to a csv. The problem I encounter is trying to iterate through each record.
Here's how the structure looks
Classes
clsAllRecordTypes
clsRecordType1
clsRecordType2
...
clsRecordType8
Collection
clsColRecords
The problem is in the retrieval
Module
Dim PayRecord As PayRecords 'Class of Classes
Dim PayRecordList As bankCollection
...code to load all the payrecords
With payrecordlist
Foreach vItem in .pObjCol
debug.print .pObjCol.Item(?) ' not sure why i can't see all 8
next vItem
End With
When I add vItem to the watch I can see each and every record type filled up with information but yet i Can not access it. Below is the Class of classes and collection
Class of Classes
Option Explicit
'This class is a representation of all the record types that apply to our Payment Manager
'It aggregates all the record types (classes) into one class. That one class is used in the main processing module for simplicty
'
Private pPayRecord As New PayRecord
Private pPNAR_OP As New PNAR_OP
Private pPNAR_RP As New PNAR_RP
Private pSuppACHREC As New SuppACHRec
Private pSuppCCRRec As New SuppCCRRec
Private pSuppCHKRec As New SuppCHKRec
Private pDocumentDelieveryRec As New DocumentDeliveryRecord
Private pInvoiceRecords As New InvoiceRecords
Public Property Get PY_PayRecord() As PayRecord
Set PY_PayRecord = pPayRecord
End Property
Public Property Let PY_PayRecord(ByVal newPayRecord As PayRecord)
Set pPayRecord = newPayRecord
End Property
Public Property Get PA_PNAR_OP() As PNAR_OP
Set PA_PNAR_OP = pPNAR_OP
End Property
Public Property Let PA_PNAR_OP(ByVal newPNAR_OP_Record As PNAR_OP)
Set pPNAR_OP = newPNAR_OP_Record
End Property
Public Property Get PA_PNAR_RP() As PNAR_RP
Set PA_PNAR_RP = pPNAR_RP
End Property
Public Property Let PA_PNAR_RP(ByVal newPNAR_RP_Record As PNAR_RP)
Set pPNAR_RP = newPNAR_RP_Record
End Property
Public Property Get AC_SuppACH() As SuppACHRec
Set AC_SuppACH = pSuppACHREC
End Property
Public Property Let AC_SuppACH(ByVal newSuppACH_Record As SuppACHRec)
Set pSuppACHREC = newSuppACH_Record
End Property
Public Property Get AC_SuppCCR() As SuppCCRRec
Set AC_SuppCCR = pSuppCCRRec
End Property
Public Property Let AC_SuppCCR(ByVal newSuppCCR_Record As SuppCCRRec)
Set pSuppCCRRec = newSuppCCR_Record
End Property
Public Property Get AC_SuppCHK() As SuppCHKRec
Set AC_SuppCHK = pSuppCHKRec
End Property
Public Property Let AC_SuppCHK(ByVal newSuppCHK_Record As SuppCHKRec)
Set pSuppCHKRec = newSuppCHK_Record
End Property
Public Property Get DocumentDeliveryRecord() As DocumentDeliveryRecord
Set DocumentDeliveryRecord = pDocumentDelieveryRec
End Property
Public Property Let DocumentDeliveryRecord(ByVal newDocumentDeliveryRecord As DocumentDeliveryRecord)
Set pDocumentDelieveryRec = newDocumentDeliveryRecord
End Property
Public Property Get InvoiceRecords() As InvoiceRecords
Set InvoiceRecords = pInvoiceRecords
End Property
Public Property Let InvoiceRecords(ByVal newInvoiceRecord As InvoiceRecords)
Set pInvoiceRecords = newInvoiceRecord
End Property
Collection Class
Option Explicit
Private pHeaderRec As New HeaderRec
Private pNewPayRecords As New PayRecords
Public pObjCol As Collection
Private pTrailerRec As New TrailerRec
Private Sub Class_Initialize()
Set pObjCol = New Collection
End Sub
Private Sub Class_Terminate()
Set pObjCol = Nothing
End Sub
Public Property Get HD_HeaderRecord() As HeaderRec
Set HD_HeaderRecord = pHeaderRec
End Property
Public Property Let HD_HeaderRecord(ByVal newHeaderRecord As HeaderRec)
Set pHeaderRec = newHeaderRecord
End Property
Sub Add(ByVal newPayRecs As PayRecords)
pObjCol.Add newPayRecs
End Sub
Property Get Count() As Long
Count = pObjCol.Count
End Property
Public Property Get TR_TrailerRecord() As TrailerRec
Set TR_TrailerRecord = pTrailerRec
End Property
Public Property Let TR_TrailerRecord(ByVal newTrailer_Record As TrailerRec)
Set pTrailerRec = newTrailer_Record
End Property

I'm sorry if this doesn't help, because your explanation is hard to follow. But, I'll assume that you are saying that you have an object of type Payrecords, which contains references to seven other objects of types PNAR_OP, PNAR_RP, etc. Each of these latter objects contain "20-30 fields" that you want to get at. You ask how to loop through all of these.
A simple way to do that is to use an array. Yes, you can foreach through Collections or (better yet) Dictionaries, but arrays work, they're easy to understand, and they were iterating through objects when Collections were running around in diapers.
Let your Payrecords have a property of type Object(6). When you initialize it, instantiate one of each of the seven objects and add it to the array (for example, "Set myPayrecordsObjects(3) = New SubCCRRec" and so on). To loop through, just use a for next loop to loop through the 7 objects.
Since you provide no information about how you structure your "fields" within these objects, I'll recommend that you iterate through the Fields collection of the ADO object to loop through those. (If you're not using the ADO Fields collection, well, your attention to detail gets mine in return.)

Related

Compile error: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions

I'm struggling with a little bit of VBa and Excel. I need to create a structure in VBa, which is a Type. The problem I have is, I get an error message when I try to execute the code! I feel I need to explain how I have arrived where I am in case I've made an error.
I have read that to create a type, it needs to be made public. As such I created a new Class (under Class Modules). In Class1, I wrote
Public Type SpiderKeyPair
IsComplete As Boolean
Key As String
End Type
And within ThisWorkbook I have the following
Public Sub Test()
Dim skp As SpiderKeyPair
skp.IsComplete = True
skp.Key = "abc"
End Sub
There is no other code. The issue I have is I get the error message
Cannot define a public user-defined type within an object module
If I make the type private I don't get that error, but of course I can't access any of the type's properties (to use .NET terminology).
If I move the code from Class1 into Module1 it works, but, I need to store this into a collection and this is where it's gone wrong and where I am stuck.
I've updated my Test to
Private m_spiderKeys As Collection
Public Sub Test()
Dim sKey As SpiderKeyPair
sKey.IsComplete = False
sKey.Key = "abc"
m_spiderKeys.Add (sKey) 'FAILS HERE
End Sub
Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions
I have looked into this but I don't understand what it is I need to do... How do I add the SpiderKeyPair to my collection?
Had the exact same problem and wasted a lot of time because the error information is misleading. I miss having List<>.
In Visual Basic you can't really treat everything as an object. You have Structures and Classes which have a difference at memory allocation: https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/structures-and-classes
A Type is a structure (so are Arrays), so you if you want a "List" of them you better use an Array and all that comes with it.
If you want to use a Collection to store a "List", you need to create a Class for the object to be handled.
Not amazing... but it is what the language has available.
You seem to be missing basics of OOP or mistaking VBA and VB.NET. Or I do not understand what are you trying to do. Anyhow, try the following:
In a module write this:
Option Explicit
Public Sub Test()
Dim skpObj As SpiderKeyPair
Dim m_spiderKeys As New Collection
Dim lngCounter As Long
For lngCounter = 1 To 4
Set skpObj = New SpiderKeyPair
skpObj.Key = "test" & lngCounter
skpObj.IsComplete = CBool(lngCounter Mod 2 = 0)
m_spiderKeys.Add skpObj
Next lngCounter
For Each skpObj In m_spiderKeys
Debug.Print "-----------------"
Debug.Print skpObj.IsComplete
Debug.Print skpObj.Key
Debug.Print "-----------------"
Next skpObj
End Sub
In a class, named SpiderKeyPair write this:
Option Explicit
Private m_bIsComplete As Boolean
Private m_sKey As String
Public Property Get IsComplete() As Boolean
IsComplete = m_bIsComplete
End Property
Public Property Get Key() As String
Key = m_sKey
End Property
Public Property Let Key(ByVal sNewValue As String)
m_sKey = sNewValue
End Property
Public Property Let IsComplete(ByVal bNewValue As Boolean)
m_bIsComplete = bNewValue
End Property
When you run the Test Sub in the module you get this:
Falsch
test1
-----------------
-----------------
Wahr
test2
Pay attention to how you initialize new objects. It happens with the word New. Collections are objects and should be initialized as well with New.

For a combobox with item = a datarow, how to point ValueMember to one of it's columns

I add items to a combobox like this:
For each R as DataRow in MyDataTable.Rows
If R("ID") > 10 then MyCombo.Items.Add(R)
Next
And now I need to set the DisplayMember and ValueMember to a column of the datarow:
MyCombo.ValueMember = R("ID")
MyCombo.DisplayMember = R("Name")
I know it doesn't make sence to to use "R" as it doesn't reference to anything at this point but it's just to make an indication of what I mean ;-)
The documentation for ValueMember says:
"A String representing a single property name of the DataSource property value, or a hierarchy of period-delimited property names that resolves to a property name of the final data-bound object"
I know I can add the rows to a new datatable and set it to the DataSource, but as you can add any object to the combobox items, it would be nice to use the rows directly, just can't figures out how to make a reference the particular column as a string.?
Maybe you cannot use a row object directly. I guess to use Valuemember you need your item objects to be wrapped in a collection which implement an ilist interface.
In the old MS-Access days combobox items had natively Display- and ValueMember properties, I've always missed that in the .Net combobox control.
My work-around is to use this class, which then can be used for all your ComboBoxes:
Class oComboItems
Public items As New List(Of oDVpairs)
Class oDVpairs
Implements IComparable(Of oDVpairs)
Private myDM As String
Private myVM As Object
Sub New(DM As String, VM As Object)
myDM = DM
myVM = VM
End Sub
Public ReadOnly Property DM() As String
Get
Return myDM
End Get
End Property
Public ReadOnly Property VM() As Object
Get
Return myVM
End Get
End Property
Public Function CompareTo(other As oDVpairs) As Integer Implements IComparable(Of oDVpairs).CompareTo
Return Me.myDM.CompareTo(other.myDM)
End Function
End Class
Public Sub AddItems(DisplayMember As String, ValueMemeber As Object)
items.Add(New oDVpairs(DisplayMember, ValueMemeber))
End Sub
Public ReadOnly Property DisplayMember() As String
Get
Return "DM"
End Get
End Property
Public ReadOnly Property ValueMember() As Object
Get
Return "VM"
End Get
End Property
End Class
And now add my datarows(or any other objects) to the ComboBox:
Dim CI As New oComboItems
For Each R As DataRow In DT_U.Rows
If R("medlnr") > 10 Then
CI.AddItems(R("name"), R("ID"))
end if
Next
CI.items.Sort()
MyCombo.DataSource = CI.Items
MyCombo.DisplayMember = CI.DisplayMember
MyCombo.ValueMember = CI.ValueMember

Object variable as object property

I have a collection of Employee objects in a collection called MyEmployees.
I want to assign one of these objects as the superior of the others.
I have created the property pSuperior in the class cEmployee
Below are the Set and Get methods for the class:
Public Property Set Superior(value As Object)
Set pSuperior = value
End Property
Public Property Get Superior() As Object
Superior = pSuperior
End Property
The Set method gives me no errors at run-time:
Set MyEmployees.Item(1).Superior = MyEmployees.Item(2)
But when I use
Debug.Print MyEmployees.Item(1).Superior.Name
to test I get run-time error 91 object variable or with block not set
Is my Set Method not working?
You always assign objects to variables (including return values of functions and properties) using Set. So the code for your Get method should be:
Public Property Get Superior() As Object
Set Superior = pSuperior
End Property
Looks like you're Get property is missing Set for your object assignment as a return value. I'm not sure what else you may have going on, so here's some sample code of what worked for me (except no collection). It may also be an improvement to define your Get/Set objects as Employees.
Here's example class code:
Private pSuperior As Employee
Private pName As String
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
Public Property Get Superior() As Employee
Set Superior = pSuperior
End Property
Public Property Set Superior(Value As Employee)
Set pSuperior = Value
End Property
And Example using the class as you described:
Sub test()
Set John = New Employee
Set Tim = New Employee
Set Tim.Superior = John
John.Name = "John"
Tim.Name = "Tim"
MsgBox Tim.Name & "'s Superior is " & Tim.Superior.Name
End Sub

VB.NET Add row data to public class / public field of type List(Of T) Object reference not set to an instance of an object

I am trying to add rows to a public class that has public fields and am getting an error: Object reference not set to an instance of an object
Public Class EmailRecipient
EmailAddress As String = ""
FullName As String = ""
End Class
Public Class EmailDetails
Public FromEmail As String = ""
Public ToEmails As List(Of Emails) = nothing
End Class
Public Sub SetEmailDetails
'Populate EmailRecipient Class
Dim er As New EmailRecipient
er.EmailAddress = "rodney#norespect.com"
er.FullName = "Rodney Dangerfield"
'Populate EmailDetails Class
Dim ed As New EmailDetails
ed.FromEmail = "sender#danger.com" 'This works fine
ed.ToEmails.Add(er) 'Here error happens
End Sub
I'm guessing I need to create an instance of the EmailRecipient class before I can add an item to it.
Not sure how to do that with a Public Field in a Public Class??
It's been a rough day. I got up this morning, put a shirt on and a button fell off. I picked up my briefcase and the handle came off. I'm afraid to go to the bathroom.
Thanks for the help :-)
I'm guessing I need to create an instance of the EmailRecipient class
You already have an instance of the EmailRecipient class. That's your er variable. You actually have two errors here. First, you explicitly set ToEmails to Nothing:
Public ToEmails As List(Of Emails) = nothing
This means that your ToEmails variable is a Null Reference. It doesn't have an actual object yet.
The second issue is that you shouldn't get that excpetion, because this shouldn't even compile. You define ToEmails as a List(Of Emails), but tried to add an object of type "EmailRecepient" to it. That should be a compiler error. If it's not, you need to turn Option Strict or Option Infer back on.
So what you really need is an instance of a the List(Of EmailRecipient) type. Fix the bad line of code like this:
Public ToEmails As New List(Of EmailRecipient)

Access variable of child form

Using VB.Net
I have about 60 Child forms
Each have a variable with same name.
In Main form I want to set the value of the variable of the active child.
One way of doing that is like
Select Case Me.ActiveMdiChild.Name
Case "formName"
frmformName.Variable=0
I donot want to do that as it involves writing many cases and I may miss some.
Is there some other way of doing it .
I tried
Dim O as Object = Me.ActiveMdiChil
O.VariableName= 0
and its various variants but its not working
Another way to do that is with an Interface, example:
Public Interface IChildVariable
Property Variable() As Integer
End Interface
Public Class Form1
Implements IChildVariable
Private _MyVariable As Integer
Public Property Variable() As Integer Implements IChildVariable.Variable
Get
Return _MyVariable
End Get
Set(ByVal value As Integer)
_MyVariable = value
End Set
End Property
End Class
Then you can just have a single check point:
If TypeOf Me.ActiveMdiChild Is IChildVariable Then
DirectCast(Me.ActiveMdiChild, IChildVariable).Variable = 0
Else
''Throw Exception
End If