Class of a Class - class

I'm getting just killed trying to make a class of a class. I have shopped around the site and seen several examples but maybe because its 1:43 I am having a hard time understanding them.
I was successfully able to use a class to automate a huge data entry project at work. I created a class called catDist which is the category distribution of types of agricultural products a company could manufacture or sell.
catDist contains six properties:
Private selfWorth As String
Private Q1 As Double
Private Q2 as Double
Private Q3 as Double
Private Q4 As Double
Private activated As Boolean
They all have the standard get and let codes.
There are 48 possible categories. I have a module that creates 48 instances of them with 48 different values for selfWorth (e.g "Cottonseed", or "maize" etc), and sets Q1 through Q4 as 0 . The module originally worked with a Userform that I could type in the values and hit enter. If it saw that I had entered a value inside a particular textbox (yes there were 48X4 textboxes) it would set activated to true and changes the relevant Q's to the values I entered.
WHAT I WANT TO DO NOW.
It was a great success. Now what I want to do is create a class called "Distributor". Each distributor class would have 4 collections have catDist objects. I can create the distributor class. I can create the catDist class. But for the love of God I can not figure out a way to set the corresponding distributor catDist property to the catDist value I used in the Set method.
Sub testRegist()
Dim registrant As testRegistrant
Set registrant = New testRegistrant
registrant.registNum = "Z000123"
'MsgBox (registrant.registNum)
Dim cowMilk As testcatDist
Set cowMilk = New testcatDist
cowMilk.selfWorth = "Cow Milk"
cowMilk.distribution = 4.6
registrant.testCat = cowMilk
Debug.Print registrant.testCat.selfWorth
End Sub
catDist Class
Private pselfWorth As String
Private pdistribution As Double
Public Property Get selfWorth() As String
selfWorth = pselfWorth
End Property
Public Property Let selfWorth(name As String)
pselfWorth = name
End Property
Public Property Get distribution() As Double
distribution = pdistribution
End Property
Public Property Let distribution(dist As Double)
pdistribution = dist
End Property
Registrant a.k.a distributor class
Private pRegistNum As String
Private pCatDist As testcatDist
Public Property Get registNum() As String
registNum = pRegistNum
End Property
Public Property Let registNum(registration As String)
pRegistNum = registration
End Property
Public Property Get testCat() As testcatDist
testCat = pCatDist
End Property
Public Property Let testCat(cat As testcatDist)
Set pCatDist = New testcatDist
pCatDist = cat
End Property

The only problem I see is that you are using Let instead of Set. In VBA you use Set when assigning to objects.
When you write registrant.testCat = cowMilk (in your Sub), testCat = pCatDist (in the getter of testRegistrant.testCat) and pCatDist = cat (in the setter of testRegistrant.testCat) you are implicitly using Let (it's as if you had written Let registrant.testCat = cowMilk) instead of (explicitly) using Set.
So, if you write Set registrant.testCat = cowMilk in your test Sub, Set testCat = pCatDist in the getter and Set pCatDist = cat in the setter you should be good to go.
Also, in the same setter, the initialization of pCatDist isn't needed since you are passing cat to it in the next line.
And, as #GSerg (thank you) says, the signature of your setter should be Public Property Set testCat(cat as testcatDist) instead of Public Property Let.

Related

What is the purpose of the line "public Word(#NonNull String word) {this.mWord = word;}" in this example?

I'm trying to figure out how to use Android's Room library for implementing a prepopulated sqlite database in my app and I came across this Android tutorial. One of the lines (the one in the title) confuses me though, because in another tutorial (also by Android), this line isn't present. Why is this line of code present in the first tutorial but not the second? What is its purpose?
I ask this because my code (which I'm basing off the second tutorial) doesn't include this line and yet this post by a different user attempting to do something similar with a prepopulated database does include it.
Here is some of the code I have (each of the fields has a getter method which just returns this.thatfield'sname):
#Entity (tableName = "words")
public class Words {
#PrimaryKey
#NonNull
#ColumnInfo (name = "word_id")
private int wordId;
#ColumnInfo(name = "a_words")
private String aWords;
#ColumnInfo(name = "b_words")
private String bWords;
#ColumnInfo(name = "c_words")
private String cWords;
This code gives me a "Cannot find setter for field" but just changing the fields from public to private seems to solve that (not sure if this is the best way to solve this error, though).
Why is this line of code present in the first tutorial but not the second?
That line is an additional class constructor that takes 1 non-null String and sets the mWord member/variable to the provided String.
Without then you can only use myWord = new Word(); to instantiate a Word object and the value would be either the default value if provided or null.
With the additional constructor then you could use both
myWord = new Word();
or
myOtherWord = new Word("A Word");
So, in short it's provided an alternative way of constructing/instantiating a new Object of that Class.
Using your code then you could have, for example :-
#Entity(tableName = "words")
class Words {
#ColumnInfo(name = "word_id")
#PrimaryKey
private int wordId;
#ColumnInfo(name = "a_words")
String aWords;
#ColumnInfo(name = "b_words")
String bWords;
#ColumnInfo(name = "c_words")
String cWords;
public void setWordId(int wordId, String aWord, String bWords, String c) {
this.wordId = wordId;
this.aWords = aWord;
this.bWords = bWords;
this.cWords = c;
}
}
Note for demonstration the parameter names use 3 different standards, ideally you would stick to a single standard/convention for naming the parameters.
So now you could use the one constructor that expects 4 parameters e.g.
myWord = new Words(1,"Apple","Banana","Cherry");
which equates to
myWord = new Words();
myWord.wordId = 1;
myWord.aWords = "Apple;
myWord.bWords = "Banana";
myWord.cWords = "Cherry";
As you have specified a constructor, the default constructor is no longer usable.
What is its purpose?
As can be seen, additional constructors, can reduce the amount of coding, there use will also prompt for the values (hence the use of useful parameter names improves i.e. c as above is not very meaningful at all (although in conjunction with the other parameters if would be better than x))

Using AutoBogus to set member property value range

I have the following classes:
Class Person
Property ID As String = Nothing
Property Firstname As String = ""
Property Lastname As String = ""
End Class
Class Account
Property AccountNumber As String = ""
Property Owners As New List(Of Person)
End Class
I wish to you utilize https://github.com/nickdodd79/AutoBogus to set a range of values from 1,000 to 10,000 for Person.ID when I instantiate an instance of the Account class like so:
Dim fk = AutoFaker.Create()
Dim acct = fk.Generate(Of Account)
Please how may I do this using AutoBogus?
The original design of AutoBogus was to generate small object graphs for unit tests. Generating the numbers you require could have a performance impact. However, if the above is what you are trying to generate and nothing more complex, then it could be small enough to succeed.
To use AutoBogus out the box you can do the following:
Dim acct = AutoFaker.Generate(Of Account, 1000)
The second parameter should be the number of accounts you want to create.
AutoBogus uses Bogus under the hood and then leverages Reflection to populate unset properties. If you do see any performance issues, you could use Bogus directly with the caveat that you need define a RuleFor for each property. The Bogus docs provide in-depth details on how to achieve this.
Nick.
Solution modified from answer provided by Bogus author bchavez at https://github.com/bchavez/Bogus/issues/394.
Sub Main
Dim personFaker = New AutoFaker(Of Person)
personFaker.RuleFor(Function(p) p.Firstname, Function(f) f.Name.FirstName)
.RuleFor(Function(p) p.Lastname, Function(f) f.Name.LastName)
.RuleFor(Function(p) p.ID, Function(f) f.Random.Int(1000,10000).ToString)
Dim accountFaker = New AutoFaker(Of Account)
accountFaker.RuleFor(Function(a) a.AccountNumber, Function(f) f.Random.Replace("###############"))
.RuleFor(Function(a) a.Owners, Function(f) New List(Of Person)(personFaker.GenerateBetween(1,5)))
accountFaker.Generate().Dump()
End Sub
Class Person
Property ID As String = Nothing
Property Firstname As String = ""
Property Lastname As String = ""
End Class
Class Account
Property AccountNumber As String = ""
Property Owners As New List(Of Person)
End Class

combining classes in vba?

I have a problem finding the right structure of my programme and I am trying to solve it using classes, without success.
I have 1 class for Patients, each Patient has the following:
Name
Operation
Surgeon
OperationDuration
Then each Surgeon should have a schedule for everyday.
Therefore, I am thinking using another class for daily schedule, which should have :
Day
TotalDuration
Something in my thinking doesn't look correct and I am struggling to understand what should I do.
1)Is my structure correct for what I want to do?
2)How can I check if a surgeon has a planned daily schedule and if he doesn't then add a patient to his schedule?
Any help would be much appreciated.
Thanks,
George
Class Patients
Private mstrName As String
Private mstrOperationDescription As String
Private mlngSurgeon As Long
Private mdblOpDuration As Double
Public Property Get Name() As String
Name = mstrName
End Property
Public Property Get OperationDescription() As String
OperationDescription = mstrOperationDescription
End Property
Public Property Get Surgeon() As Long
Surgeon = mlngSurgeon
End Property
Public Property Get OpDuration() As Double
OpDuration = mdblOpDuration
End Property
Class Schedule
Private mlngDay As Long
Private mdblTotalDuration As Double
Public Property Get Day() As Long
Day = mlngDay
End Property
Public Property Let Day(ByVal lDay As Long)
mlngDay = lDay
End Property
Public Property Get TotalDuration() As Double
TotalDuration = mdblTotalDuration
End Property
Public Property Let TotalDuration(ByVal dTotalDuration As Double)
mdblTotalDuration = dTotalDuration
End Property
Test Sub calculating the total duration but I am not able to list them according to days count
Public Sub Test()
Dim mydata As New clsData
Dim schedule1 As New clsSchedule
Dim schedule2 As New clsSchedule
Dim i As Integer
mydata.InputData
For i = 1 To mydata.PatientCount
If mydata.patient(i).Surgeon = 1 Then
schedule1.TotalDuration = schedule1.TotalDuration + mydata.patient(i).OpDuration
Else
schedule2.TotalDuration = schedule2.TotalDuration + mydata.patient(i).OpDuration
End If
Next i
MsgBox "Total Duration is: " & schedule1.TotalDuration
End Sub
One solution would be to have a Collection of Surgeon objects and a Collection of Patient objects. Each Patient has a Surgeon as a property, and each Surgeon has a Collection of Patients.
From this you can find out the total operation duration across all patients belonging to a Surgeon.
The trick is to know how the objects relate to each other - in this case each Patient has one Surgeon, and each Surgeon can have many Patients. This leads quite logically to the following structure:
Patient
Private mstrName As String
Private mstrOperationDescription As String
Private mobjSurgeon As cSurgeon
Private mdblOpDuration As Double
Property Let Name(txt As String)
mstrName = txt
End Property
Property Let OperationDescription(txt As String)
mstrOperationDescription = txt
End Property
Property Let Surgeon(objSurgeon As cSurgeon)
Set mobjSurgeon = objSurgeon
End Property
Property Let OpDuration(num As Double)
mdblOpDuration = num
End Property
Property Get OpDuration() As Double
OpDuration = mdblOpDuration
End Property
Surgeon
Private mstrName As String
Private mlngSurgeonId As Long
Private mcolPatients As Collection
Private Sub Class_Initialize()
Set mcolPatients = New Collection
End Sub
Property Let Name(txt As String)
mstrName = txt
End Property
Property Get Name() As String
Name = mstrName
End Property
Property Let IdNumber(num As Long)
mlngSurgeonId = num
End Property
Property Get IdNumber() As Long
IdNumber = mlngSurgeonId
End Property
Sub AddPatient(objPatient As cPatient)
mcolPatients.Add objPatient, objPatient.Name
End Sub
Function TotalHours() As Double
Dim objPatient As cPatient
For Each objPatient In mcolPatients
TotalHours = TotalHours + objPatient.OpDuration
Next objPatient
End Function
Test Routine
Sub CheckSurgeonHours()
Dim colSurgeons As Collection
Set colSurgeons = New Collection
Dim colPatients As Collection
Set colPatients = New Collection
'Populate Surgeon and Patient collections from input data
'This is the static data for each object, i.e. name, Id, operation type/duration
Dim objSurgeon As cSurgeon
For Each objSurgeon In colSurgeons
Dim objPatient As cPatient
For Each objPatient In colPatients
objPatient.Surgeon = objSurgeon
objSurgeon.AddPatient objPatient
Next objPatient
Debug.Print objSurgeon.TotalHours
Next objSurgeon
End Sub

MVVM: list in viewmodel consists of entities but I don't think it's right to place logic in entity poco classes

I have a viewmodel and it exists out of entity framework classes (just plain classes). I Read on stackoverflow that it's okey to put an inotifypropertychanged in the model. So I did that in order not to have a lot of extra code.
So, now I have to perform an action when a property changes in one of my list items... the thing is, I can't react on this, in my viewmodel...
What do I do?
I have come across this issue once I started making larger projects with the MVVM pattern.
In the end I just shifted my INotifyPropertyChanged event calls to the ViewModel properties. I also decorated these properties with validation notification as that is where most of the validation that needs a user to react to will be held, any common elements can also be pulled out to a base ViewModel where relevant.
Another issue that you might come across as time goes by is where to put some of your processing logic. I used to have mine at the Model level, but then some of them required a bit closer interaction with the UI (validation reasons mainly), but if I put it in the ViewModel then I would have to have references to my repositories etc. In the end my ViewModels have access to another layer (a Service Layer) where this takes place. (A prime example for the use of this would be if you have a column in a database which is unique that you need to check when a user has created/modified data belonging to this column.)
Anyway, that is just a bit of extra information that I had to figure out shortly after the issue your currently having!
i fought with the best stategy for this for a while. I found that there are a number of different ways to do this.
First thing to note, if you add the property validation/notification directly to your model classes then the notifictaion and validation will happen when the class is created and for each object that gets created in observable collections, which in turn can cause performance issues or other challenges. So for that reason I moved my validation and notification into a "helper" partial class for the model.
So the model gets created through the Entity framework, lets say for an object called Job. I create public partial class called job as well. Here is an example (in vb, I can convert to C# if you need)
Partial Public Class job
Inherits ValidationBase
#Region "CONSTRUCTORS"
Public Sub New()
''default values
Me.FTC_Type = 4
Me.dtCreated = Now
Me.dtUpdated = Now
HasChanges = False
End Sub
Public Sub New(bValidate As Boolean)
PropertyValitaion(bValidate)
''default values
Me.FTC_Type = 4
Me.dtCreated = Now
Me.dtUpdated = Now
HasChanges = False
End Sub
Public ReadOnly Property DisplayPath
Get
Return "W" + idJob.ToString + ": " + chrTitle + " - " + client.chrCompany
End Get
End Property
Public ReadOnly Property SearchPath
Get
Return "W" + idJob.ToString + " " + chrTitle + " " + client.chrCompany + " " + chrContact
End Get
End Property
#End Region
#Region "VALIDATION FUNCTIONS"
Public Overrides Function Validate(validationContext As ComponentModel.DataAnnotations.ValidationContext) As IEnumerable(Of ComponentModel.DataAnnotations.ValidationResult)
Return MyBase.Validate(validationContext)
PropertyValitaion(True)
End Function
Public Sub PropertyValitaion(bAllProperties As Boolean, Optional sProperty As String = "")
'initialize validation helper
If bAllProperties OrElse sProperty = "chrTitle" Then
If String.IsNullOrEmpty(chrTitle) Then
AddError("chrTitle", "You must enter a Job Title")
Else
RemoveError("chrTitle")
End If
End If
End Sub
#End Region
End Class
So you can see that I can create other readonly properties (Like display title etc) and create my own custom validation that gets called when I want. THis validation uses the IDataErrorInfo for getting tied back into the UI. I have two constructors, one the calls the property validation and one that does not. That way I can control when it happens.
I created a ValidationBase class that my Objects partial class inhertis from. THis saves me from having to manually implement IdataErrorInfo and INotifyPropertyCHnaged in every partial class for every object.
Here is my validation base:
Imports System.ComponentModel
Imports System.Collections.Concurrent
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema
Public Class ValidationBase
Implements IValidatableObject, IDataErrorInfo, INotifyPropertyChanged
#Region "DECLARATIONS"
Protected _propertyErrors As New Dictionary(Of String, String)
Protected _validationResults As New List(Of ValidationResult)
Public ReadOnly Property HasErrors() As Boolean
Get
Return (_propertyErrors.Count + _validationResults.Count) > 0
End Get
End Property
#End Region
#Region "IValidatableObject IMPLEMENTATION"
Public Overridable Function Validate(validationContext As ValidationContext) As IEnumerable(Of ValidationResult) Implements IValidatableObject.Validate
Return Nothing
End Function
#End Region
#Region "iDataError OBJECTS"
'Returns an error message
'In this case it is a general message, which is
'returned if the list contains elements of errors
Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
If _propertyErrors.Count > 0 Then
Return "Object data is invalid"
Else
Return Nothing
End If
End Get
End Property
Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
If _propertyErrors.ContainsKey(columnName) Then
Return _propertyErrors(columnName).ToString
Else
Return Nothing
End If
End Get
End Property
#End Region
#Region "IDataError FUNCTIONS"
'Adds an error to the collection, if not already present
'with the same key
Protected Sub AddError(ByVal columnName As String, ByVal msg As String)
If Not _propertyErrors.ContainsKey(columnName) Then
_propertyErrors.Add(columnName, msg)
OnPropertyChanged(columnName)
End If
End Sub
'Removes an error from the collection, if present
Protected Sub RemoveError(ByVal columnName As String)
If _propertyErrors.ContainsKey(columnName) Then
_propertyErrors.Remove(columnName)
OnPropertyChanged(columnName)
End If
End Sub
Public Sub ClearErrors()
_propertyErrors.Clear()
End Sub
#End Region
#Region "INotifyPropertyChanged IMPLEMENTATION"
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Overridable Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
End Class
SO i can't say this is the totally right way because there does not seem to be a clear best pratcie defined out there. But this works for me and I hope it can help you.

subobjects of class module not working in vba for excel

I've searched for a bit, but I couldn't find a similar enough question/answer. So here it goes:
I have a class object called Project. A Project can have multiple Scenarios assoicated with it.
I've created the class modules for each object. But I am having difficulty in, I believe, instantiating the Scenarios collection for a given Project.
Here are the class modules:
1) cProject:
Private pProjectID As Integer
Private pName As String
Private pDateCreated As String
Private pScenarios As cScenarios
' PROPERTIES
Public Property Get ProjectID() As Integer
ProjectID = pProjectID
End Property
Public Property Let ProjectID(value As Integer)
pProjectID = value
End Property
Public Property Get name() As String
name = pName
End Property
Public Property Let name(value As String)
pName = value
End Property
Public Property Get Scenarios() As cScenarios
Set Scenarios = pScenarios
End Property
Public Property Set Scenarios(value As cScenarios)
Set pScenarios = value
End Property
2) cScenarios collection class module:
Private pScenarios As Collection
Private Sub Class_Initialize()
Set pScenarios = New Collection
End Sub
Private Sub Class_Terminate()
Set pScenarios = Nothing
End Sub
Public Function Item(index As Variant) As cScenario
Set Item = pScenarios.Item(index)
End Function
Public Property Get Count() As Long
Count = pScenarios.Count
End Property
Public Sub Add(obj As cScenario)
pScenarios.Add obj
End Sub
Public Sub Remove(index As Variant)
pScenarios.Remove index
End Sub
And finally (3) the Scenario class object:
Private pScenarioID As Integer
Private pName As String
Private pDateCreated As String
Private pParent As cProject
Public Property Get ScenarioID() As Integer
ScenarioID = pScenarioID
End Property
Public Property Let ScenarioID(value As Integer)
pScenarioID = value
End Property
Public Property Get name() As String
name = pName
End Property
Public Property Let name(value As String)
pName = value
End Property
Public Property Get parent() As cProject
parent = pParent
End Property
Public Property Let parent(value As cProject)
pParent = value
End Property
Here is a standard module:
Sub test1()
Dim cS As cScenarios
Dim s As cScenario
Set cS = New cScenarios
For i = 1 To 3
Set s = New cScenario
s.name = "s" & i
cS.Add s
Next
Debug.Print cS.Item(3).name
Debug.Print cS.Count
End Sub
This works. All is good. For now. I am able to populate cS with multiple scenarios. However, if I reference the scenarios collection as a child object of the project (see below in test2() ), I get a "Run-time error '91': Object variable or With block variable not set" triggered on the cs.Add call.
Sub test2()
Dim p As cProject
Dim cS As cScenarios
Dim s As cScenario
Set p = New cProject
Set cS = p.Scenarios
For i = 1 To 3
Set s = New cScenario
s.name = "s" & i
cS.Add s
Next
Debug.Print cS.Item(3).name
Debug.Print cS.Count
End Sub
What did I do wrong building my class modules and/or how do I fix it? Thanks.
You are not initializing pScenarios in the cProject class before trying to access it with Add().
You can fix that by adding an initializer to cProject:
Private Sub Class_Initialize()
Set pScenarios = New cScenarios
End Sub
This will guarantee that the cS instance will not be Nothing when you try to invoke Add on it inside test2.
Another way (weaker IMO) would be to set p.Scenarios = new cScenarios after newing up p inside test2.
Also, make sure that the property setter for cScenario.parent is Property Set instead of Property Let.