Trying to trouble Object required error in VBA - sql

I got this problem. I have a form that retrieves a table data using the forms' record source property. When the form's opened, I set its record source property to a module's public method RetrieveMembers. Here's the code below.
Private Sub Form_Open(Cancel As Integer)
'set Form's record source property to retrieve a Members table
Me.RecordSource = mod_JoinMember.RetrieveMembers
End Sub
'mod_JoinMember Class
Public Function RetrieveMembers() As String
Dim strSQL As String
Set strSQL = "SELECT tbl_Member.Title, tbl_Member.Gender, tbl_Member.LastName,
tbl_Member.DateofBirth, tbl_Member.Occupation, tbl_Member.PhoneNoWork,
tbl_Member.PhoneNoHome, tbl_Member.MobileNo, tbl_Member.Email,
tbl_Member.Address, tbl_Member.State, tbl_Member.Postcode FROM tbl_Member;"
RetrieveMembers = strSQL
End Function
Object required error is thrown.
I couldn't comprehend this compile error. I see no wrong with my code since recordsource is a String type property. And my module's function Retrievemembers is returning a String value.
Why is it that it's not satisfied with this?

Thanks for your help.
I fixed it. The reason was because String is not really an Object to begin with. So the 'Set' keyword is not needed - since you don't need to explicitly declare String-type objects anyway!
All good now!

As you are working with a class module I think you will need to use:
Public Property Get RetrieveMembers() As String
Rather than:
Public Function RetrieveMembers() As String

Related

Create class method with already existing name from a DLL

I come from this post : Create class method with already existing name, where I explain that I need to make code compatible with MS Access and PostgreSQL. My first thought was to create a new class for PostgreSQL support and rewrite the functions previously used. The solution brought could work, but the problem is that the MS Access database class is loaded from a DLL (Microsoft Shared\DAO\dao360.dll, DAO.DBEngine). How can I interface or override those DLL functions and get rid of this "ambiguous name detected" error ?
As said in the answer to your first post you can use the Implements feature to do this. Without further details and yout code it is just more than difficulut to tell where your issue is.
IDatabase could look like that
Option Explicit
Sub OpenDB(fileName As String)
End Sub
Function ReadData(lngNr As Long) As String
End Function
clsAccess like that
Option Explicit
Implements IDatabase
Dim m_Dbs As DAO.Database
Dim m_Rcd As DAO.Recordset
Dim m_Filename As String
Sub IDatabase_OpenDB(fileName As String)
m_Filename = fileName
Set m_Filename = OpenDatabase(m_Filename, , True)
End Sub
Function IDatabase_ReadData(lngNr As Long) As String
Set m_Rcd = m_dbs.OpenRecordset("qryTable", dbOpenSnapshot)
m_Rcd.Move lngNr - 1
IDatabase_ReadData = m_Rcd.Fields("fldName").Value
m_Rcd.Close
End Function
You need to add the references in Tools/References.

Getting a collection property of a class take a property of another class of another type?

I wanted to first thank you all for the help you've given me implicitly over the last few months! I've gone from not knowing how to access the VBA IDE in Excel to writing fully integrated analysis programs for work. I couldn't have done it without the community here.
I'm currently trying to overhaul the first iteration of a data analysis program I wrote while learning how to code in VBA. While purpose driven and only really legible to myself, the code worked; but was a mess. From folks on this site I picked up Martin's Clean Code and gave it a read on how to try and be a better programmer.
From Martin's Clean Code, it was impressed on me to prioritize abstraction and decoupling of my code to allow for higher degrees of maintenance and modularization. I found this out the hard way since very minor changes requested above my pay grade would require massive and confusing rewrites! I'm trying to eliminate that problem going forward.
I am attempting to rewrite my code in terms of single responsibility classes (at least, where it is possible) and I am a bit confused. I apologize if my question isn't clear or if I'm using the wrong terminology. I want to be able to generate a collection of specific strings (the names of our detectors to be specific) with no duplicates from raw instrument data files from my lab. The purpose of this function is to assemble a bunch of metadata in a class and use it to standardize our file system and prevent clerical errors from newbies and old hands when they use the analysis program.
The testing initialization sub is below. It pops open a userform asking for the user to select the filepaths of the three files in the rawdatafiles class; then it kills the userform to free memory. The metadata object is currently for testing and will be rewritten properly when I get the output I want:
Sub setup()
GrabFiles.Show
Set rawdatafiles = New cRawDataFiles
rawdatafiles.labjobFile = GrabFiles.tboxLabJobFile.value
rawdatafiles.rawdatafirstcount = GrabFiles.tboxOriginal.value
rawdatafiles.rawdatasecondcount = GrabFiles.tboxRecount.value
Set GrabFiles = Nothing
Dim temp As cMetaData
Set temp = New cMetaData
temp.labjobName = rawdatafiles.labjobFile
'this works fine!
temp.detectorsOriginal = rawdatafiles.rawdatafirstcount
' This throws run time error 424: Object Required
End Sub
The cMetadata class I have currently is as follows:
Private pLabjobName As String
Private pDetectorsOriginal As Collection
Private pDetectorsRecheck As Collection
Private Sub class_initialize()
Set pDetectorsOriginal = New Collection
Set pDetectorsRecheck = New Collection
End Sub
Public Property Get labjobName() As String
labjobName = pLabjobName
End Property
Public Property Let labjobName(fileName As String)
Dim FSO As New FileSystemObject
pLabjobName = FSO.GetBaseName(fileName)
Set FSO = Nothing
End Property
Public Property Get detectorsOriginal() As Collection
detectorsOriginal = pDetectorsOriginal
End Property
Public Property Set detectorsOriginal(originalFilepath As Collection)
pDetectorsOriginal = getDetectors(rawdatafiles.rawdatafirstcount)
End Property
When I step through the code it starts reading the "public property get rawdatafirstcount() as string" and throws the error after "End Property" and points back to the "temp.detectorsOriginal = rawdatafiles.rawdatafirstcount" line in the initialization sub.
I think I'm at least close because the temp.labjobName = rawdatafiles.labjobFile code executes properly. I've tried playing around with the data types since this is a collection being assigned by a string but I unsurprisingly get data type errors and can't seem to figure out how to proceed.
If everything worked the way I want it to, the following function would take the filepath string from the rawdatafiles.rawdatafirstcount property and return for me a collection containing detector names as strings with no duplicates (I don't know if this function works exactly the way I want since I haven't been able to get the filepath I want to parse properly in the initial sub; but I can deal that later!):
Function getDetectors(filePath As String) As Collection
Dim i As Integer
Dim detectorsCollection As Collection
Dim OriginalRawData As Workbook
Set OriginalRawData = Workbooks.Open(fileName:=filePath, ReadOnly:=True)
Set detectorsCollection = New Collection
For i = 1 To OriginalRawData.Worksheets(1).Range("D" & Rows.Count).End(xlUp).Row
detectorsCollection.Add OriginalRawData.Worksheets(1).Cells(i, 4).value, CStr(OriginalRawData.Worksheets(1).Cells(i, 4).value)
On Error GoTo 0
Next i
getDetectors = detectorsCollection
Set detectorsCollection = Nothing
Set OriginalRawData = Nothing
End Function
Thanks again for reading and any help you can offer!
temp.detectorsOriginal = rawdatafiles.rawdatafirstcount
' This throws run time error 424: Object Required
It throws an error because, as others have already stated, the Set keyword is missing.
Now with that out of the way, a Set keyword is NOT what you want here. In fact, sticking a Set keyword in front of that assignment will only buy you another error.
Let's look at this property you're invoking:
Public Property Get detectorsOriginal() As Collection
detectorsOriginal = pDetectorsOriginal
End Property
Public Property Set detectorsOriginal(originalFilepath As Collection)
pDetectorsOriginal = getDetectors(rawdatafiles.rawdatafirstcount)
End Property
You're trying to assign detectorsOriginal with what appears to be some String value that lives in some TextBox control on that form you're showing - but the property's type is Collection, which is an object type - and that's not a String!
Now look at the property that does work:
Public Property Get labjobName() As String
labjobName = pLabjobName
End Property
Public Property Let labjobName(fileName As String)
Dim FSO As New FileSystemObject
pLabjobName = FSO.GetBaseName(fileName)
Set FSO = Nothing
End Property
This one is a String property, with a Property Let mutator that uses the fileName parameter it's given.
The broken one:
Public Property Set detectorsOriginal(originalFilepath As Collection)
pDetectorsOriginal = getDetectors(rawdatafiles.rawdatafirstcount)
End Property
Is a Set mutator, takes a Collection parameter, and doesn't use the originalFilepath parameter it's given at all!
And this is where I'm confused about your intention: you're passing what has all the looks of a String except for its type (Collection) - the calling code wants to give it a String.
In other words the calling code is expecting this:
Public Property Let detectorsOriginal(ByVal originalFilepath As String)
See, I don't know what you meant to be doing here; it appears you're missing some pOriginalFilepath As String private field, and then detectorsOriginal would be some get-only property that returns some collection:
Private pOriginalFilePath As String
Public Property Get OriginalFilePath() As String
OriginalFilePath = pOriginalFilePath
End Property
Public Property Let OriginalFilePath(ByVal value As String)
pOriginalFilePath = value
End Property
I don't know what you're trying to achieve, but I can tell you this:
Don't make a Property Set member that ignores its parameter, it's terribly confusing code.
Don't make a Property (Get/Let/Set) member that does anything non-trivial. If it's not trivially simple and has a greater-than-zero chance of throwing an error, it probably shouldn't be a property. Make it a method (Sub, or Function if it needs to return a value) instead.
A word about this:
Dim FSO As New FileSystemObject
pLabjobName = FSO.GetBaseName(fileName)
Set FSO = Nothing
Whenever you Dim something As New, VBA will automatically instantiate the object whenever it's referred to. In other words, this wouldn't throw any errors:
Dim FSO As New FileSystemObject
Set FSO = Nothing
pLabjobName = FSO.GetBaseName(fileName)
Avoid As New if you can. In this case you don't even need a local variable - use a With block instead:
With New FileSystemObject
pLabjobName = .GetBaseName(fileName)
End With
May not be your issue but you're missing Set in your detectorsOriginal Set/Get methods:
Public Property Get detectorsOriginal() As Collection
Set detectorsOriginal = pDetectorsOriginal
End Property
Public Property Set detectorsOriginal(originalFilepath As Collection)
Set pDetectorsOriginal = getDetectors(rawdatafiles.rawdatafirstcount)
End Property
So the error is one I've made a time or two (or more). Whenever you assign an object to another object, you have to use the Set reserved word to assign the reference to the Object.
In your code do the following:
In Sub setup()
Set temp.detectorsOriginal = rawdatafiles.rawdatafirstcount
And in the cMetadata class change the Public Property Set detectorsOriginal(originalFilepath As Collection) property to the following:
Public Property Get detectorsOriginal() As Collection
Set detectorsOriginal = pDetectorsOriginal
End Property
Public Property Set detectorsOriginal(originalFilepath As Collection)
Set pDetectorsOriginal = getDetectors(rawdatafiles.rawdatafirstcount)
End Property
Also in your function Function getDetectors(filePath as String) as Collection change the statement afterNext i` to
Set getDetectors = detectorsCollection
Also, I'm very glad to hear that you've learned how to use VBA.
When you're ready to create your own Custom Collections, check out this post. Your own custom Collections.
I also book marked Paul Kelly's Excel Macro Mastery VBA Class Modules – The Ultimate Guide as well as his Excel VBA Dictionary – A Complete Guide.
If you haven't been to Chip Pearson's site you should do so. He has a ton of useful code that will help your delivery your projects more quickly.
Happy Coding.

VBA Class with Collection of itself

I'm trying to create a class with a Collection in it that will hold other CASN (kind of like a linked list), I'm not sure if my instantiation of the class is correct. But every time I try to run my code below, I get the error
Object variable or With block not set
CODE BEING RUN:
If (Numbers.count > 0) Then
Dim num As CASN
For Each num In Numbers
If (num.DuplicateOf.count > 0) Then 'ERROR HERE
Debug.Print "Added " & num.REF_PO & " to list"
ListBox1.AddItem num.REF_PO
End If
Next num
End If
CLASS - CASN:
Private pWeek As String
Private pVendorName As String
Private pVendorID As String
Private pError_NUM As String
Private pREF_PO As Variant
Private pASN_INV_NUM As Variant
Private pDOC_TYPE As String
Private pERROR_TEXT As String
Private pAddressxl As Range
Private pDuplicateOf As Collection
'''''''''''''''' Instantiation of String, Long, Range etc.
'''''''''''''''' Which I know is working fine
''''''''''''''''''''''
' DuplicateOf Property
''''''''''''''''''''''
Public Property Get DuplicateOf() As Collection
Set DuplicateOf = pDuplicateOf
End Property
Public Property Let DuplicateOf(value As Collection)
Set pDuplicateOf = value
End Property
''''' What I believe may be the cause
Basically what I've done is created two Collections of class CASN and I'm trying to compare the two and see if there are any matching values related to the variable .REF_PO and if there is a match I want to add it to the cthisWeek's collection of class CASN in the DuplicateOf collection of that class.
Hopefully this make sense... I know all my code is working great up to this point of comparing the two CASN Collection's. I've thoroughly tested everything and tried a few different approaches and can't seem to find the solution
EDIT:
I found the error to my first issue but now a new issue has appeared...
This would be a relatively simple fix to your Get method:
Public Property Get DuplicateOf() As Collection
If pDuplicateOf Is Nothing Then Set pDuplicateOf = New Collection
Set DuplicateOf = pDuplicateOf
End Property
EDIT: To address your question - "So when creating a class, do I want to initialize all values to either Nothing or Null? Should I have a Class_Terminate as well?"
The answer would be "it depends" - typically there's no need to set all your class properties to some specific value: most of the non-object ones will already have the default value for their specific variable type. You just have to be aware of the impact of having unset variables - mostly when these are object-types.
Whether you need a Class_Terminate would depend on whether your class instances need to perform any "cleanup" (eg. close any open file handles or DB connections) before they get destroyed.

VBA global class variable

My obstacle is trying to get multiple subs to recognize class variables. When I try to declare them globally, I get a compile error: "Invalid outside procedure". Then, when I run a public function or sub to declare the variables, they remain undefined in the other subs. I want multiple subs to recognize the variables because their values are supposed to be altered via UserForm, and then utilized in a different sub.
If it could work in this manner, great, but I understand that my design could fundamentally be flawed. Please advise!
This is my Class definition, inserted as a Class module named "cRSM":
Option Explicit
Private pName As String
Private pDesiredGrowth As Double
'Name of RSM
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
'Growth property
Public Property Get DesiredGrowth() As Double
DesiredGrowth = pDesiredGrowth
End Property
Public Property Let DesiredGrowth(Value As Double)
If Value > 0 And Value < 1 Then
pDesiredGrowth = Value
End If
End Property
This is invalid procedure error (which I put in the Global Declarations section):
'Bedoya
Dim Bedoya As cRSM
Set Bedoya = New cRSM
Bedoya.Name = "Bedoya"
And this is the "variable not defined error" (within a private sub):
Private Sub Add_Click()
**Bedoya.DesiredGrowth** = Txt2.Value
Thank you for your time
In a standard module (I name mine MGlobals), put
Public Bedoya As cRSM
Then in another standard module (I name mine MOpenClose), put
Sub Initialize()
If Not Bedoya Is Nothing Then
Set Bedoya = New cRSM
End If
End Sub
Any default properties you want set should be set in the Class_Initialize procedure. In any procedure that you want to use Bedoya, use
Initialize
and it will instantiate the global variable if necessary. The only difference between this and the New keyword is that you can't accidentally instantiate the variable with this method. You either call Initialize or you don't. A lot of VBA developers use New, but almost never do for that reason.
If I understood well You want a global object.
You can put the declaration in module like
public Bedoya As cRSM
then you have create the object ... you can use a global event inside the Workbook like
Private Sub Workbook_Open()
Set Bedoya = New cRSM
Bedoya.initialize("Bedoya") 'a method to initialize private variables
End Sub
Now you can use the global object. You have to restart the excel file or run this method manually.
Is not good style to use global variables, but sometimes is the more easy to do :P
What you want to do nowadays is done using singleton Software Pattern, but this is for other day hehehe

Why can't I bind a list of custom objects to datagridview?

I have looked through your questions as well as elsewhere on the internet for the past two hours and cannot find a solution for my problem anywhere, or at least I didn't understand it if I did. I apologize in advance if this appears redundant or inane. Let me be clear: the issue is that I am somehow NOT implementing the approach correctly, but I understand (or think I do) how it is supposed to be done.
I have a gridview on a form in which I want to display custom objects representing appointments. I want to bind to my appointment objects not a datatable (which was successful). However, the below approach will not display my appointment objects in the grid although it appears correct. Furthermore, adding objects directly to the bindingsource's internal list also fails to show them in the grid, as does setting the datasource of the gridview to the bindinglist directly. I have no idea what I am doing wrong! Please help, this seems to make no sense at all and is driving me crazy.
Public Sub DisplayItems()
Dim bindingsource As BindingSource
Dim appointment As ClsAppointment
Dim appointments As System.ComponentModel.BindingList(Of ClsAppointment)
Dim iterator As IEnumerator
appointments = New System.ComponentModel.BindingList(Of ClsAppointment)
bindingsource = New BindingSource
iterator = Items
While iterator.MoveNext '
appointment = iterator.Current
appointments.Add(appointment)
End While
bindingsource.DataSource = appointments
gridview.DataSource = bindingsource
Debug.Print("")
Debug.Print("DisplayItems()...")
Debug.Print("GridView has " & gridview.Rows.Count & " rows")
End Sub
Public Class ClsAppointment
Public FirstName As String
Public LastName As String
Public Day As String
Public [Date] As Date
Public Time As Date
Public Address As String
Public City As String
Public State As String
Public Zip As String
Public Description As String
End Class
========================================================================================
Note: DisplayItems() is a method of an adapter (ItemEditor) which I chose not to show for simplicity's sake. Another method (Items) returns the adapter's collection of items (appointments) via an enumerator. I have tested this and know that the enumerator is returning the items so the problem is not this.
You can not bind to public fields of an object. As Microsoft states "You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object." Msdn- Binding Sources Overview.
Change your ClsAppointment class to this :
Public Class ClsAppointment
Property FirstName As String
Property LastName As String
Property Day As String
Property [Date] As Date
Property Time As Date
Property Address As String
Property City As String
Property State As String
Property Zip As String
Property Description As String
End Class
Allow me to simplify your code:
Public Sub DisplayItems()
gridview.DataSource = Me.Items()
Debug.Print("")
Debug.Print("DisplayItems()...")
Debug.Print("GridView has " & gridview.Rows.Count & " rows")
End Sub
Try this, and let us know what errors you get. I know you might eventually need the BindingSource, but for the moment let's cut that out of the picture and see how things work.