What I'm trying to do / the problem:
I'm effectively building a task list with a tree format; this would be made up of two class types Task_List and Task.
Task_Lists contain other Task_Lists or Tasks and many of their properties are the sum of the 'same' properties of the children that it contains. For example a property might be Duration, and so the Duration of a Task_List would be the sum of all the Durations of the Tasks and Task_Lists contained within it.
I've worked out how to do most of this (albeit after being stuck for quite awhile trying to find out how to access a parent class from a child one), but what I'm struggling with is an eloquent way to update the properties of a Task_List when the relevant property of one of its children is updated. The two methods I can get to work right now are both, in my opinion, flawed; for different reasons.
Option 1)
Method: Every time a value is changed for parameter X in a child, trigger a public function in the parent which resets X in the parent, then iterates through all the children and sums X; this is then set as the parent's value.
My issue with this: Whilst this works it feels messy and also perhaps 'wasteful' in that it has to loop through all children any time one of them is changed. If there's a lot of tasks and each of them is changed this could stack-up.
Option 2)
Method: Have the Task_List also have a Let property for X, and have the child call it to first remove its old value of X, and then add its new value of X.
My issue with this: Less 'wasteful' than Option 1, however this leaves the parent open to having its value for X changed by something other than the child. It would be nice to restrict it in some way so that it can only be called by a child.
Option 3)
Method: Basically the same as Option 2 but instead of passing a value through the function call, pass the child in its entirety. Then have the parent check that what has been passed is actually one of its children (or at least that the child reports that the parent is its parent; not perfect but I think that would be good enough and it's easier to implement imo).
My issue with this: I can't get it to work; and I have a feeling in VBA it might not be possible?
Any help, guidance, etc. would be greatly appreciated; and if any further information/clarification is required please just say and I'll provide it via edits/comments :)
Simplified example code:
Task class module:
Private Type Task
Name As String
Value As Long
Parent as Task_List
End Type
Private self As Task
Public Sub Initialize(Parent as Task_List)
Set self.Parent = Parent
End Sub
Public Property Get Parent() as Task_List
Set Parent = Self.Parent
End Property
Property Let Name(ByVal Name As String)
self.Name = Name
End Property
Property Get Name() As String
Name = self.Name
End Property
' I've put both options 2 & 3 here, but naturally only one would really be used at a time.
Property Let Value(ByVal Value As Long)
Call self.Parent.Option_2(-self.Value)
Call self.Parent.Option_3(self,-1)
self.Value = Value
Call self.Parent.Option_3(self,1)
Call self.Parent.Option_2(self.Value)
End Property
Property Get Value() As Long
Value = self.Value
End Property
Task list class:
Private Type Task_List
Name As String
SubTasks As New Collection
Value As Long
End Type
Private self As Task_List
Public Property Get SubTasks() As Collection
Set SubTasks = self.SubTasks
End Property
Public Property Let SubTasks(SubTask_Coll As Collection)
Set self.SubTasks = SubTask_Coll
End Property
Property Let Name(ByVal Name As String)
self.Name = Name
End Property
Property Get Name() As String
Name = self.Name
End Property
Property Get Value() As Long
Value = self.Value
End Property
Public Sub Option_2(ByVal Update_Value as long, _
Optional ByVal Multiplier as Long = 1)
self.Value = self.Value (Multiplier * Update_Value)
End Sub
Public Sub Option_3(ByVal Child_Task as Variant, _
Optional ByVal Multiplier as Long = 1)
On Error Resume Next
If Child_Task.Parent = self Then
self.Value = self.Value (Multiplier * Child_Task.Value)
End If
On Error GoTo 0
End Sub
Public Sub Option_1()
Dim Cur_Sum As Long, Cur_Task as Variant
For Each Cur_Task In self.SubTasks
Cur_Sum = Cur_Sum Cur_Task.Value
Next Cur_Task
self.Value = Cur_Sum
End Sub
Example of them in use:
Sub Example()
Dim Main_List As New Task_List
Main_List.Name = "Bake Bread"
Dim T1 As New Task
T1.Initialize Main_List
Main_List.Subtasks.Add T1
T1.Name = "Buy Ingredients"
T1.Value = 250
Dim T2 As New Task
T2.Initialize Main_List
Main_List.Subtasks.Add T2
T2.Name = "Do Baking"
T2.Value = 400
Debug.Print Main_List.SubTasks(1).Name
Debug.Print Main_List.SubTasks(2).Name
Debug.Print Main_List.Value
End Sub
CodePudding user response:
So I've found the issue was trying to pass "self" when those needed to be "Me"s (I also 'TIL'ed that On Error Resume Next
causes errors in If statements to 'activate' them as if they were True).
So the Update_Value function in the Task_List class becomes:
Public Sub Update_Value(ByVal Child_Task as Variant, _
Optional ByVal Multiplier as Long = 1)
On Error GoTo Exit_Sub
If Child_Task.Parent Is Me Then
self.Value = self.Value (Multiplier * Child_Task.Value)
End If
On Error GoTo 0
Exit_Sub:
End Sub
And the Let Value property of the Task class becomes:
Property Let Value(ByVal Value As Long)
Call self.Parent.Update_Value(Me, -1)
self.Value = Value
Call self.Parent.Update_Value(Me, 1)
End Property
(In the real code I have an Enum for Multiplier to restrict it to just -1 and 1).
Additionally, I've used this ability (of using Me) to modify the Initialize sub in the Task class to also add it to the parent whilst defining the parent. Thus:
Public Sub Initialize(Parent as Task_List)
Set self.Parent = Parent
Parent.SubTasks.Add Me
End Sub