Totals and collections

Totals and collections

Old forum URL: forums.lhotka.net/forums/t/1605.aspx


Wal972 posted on Thursday, October 26, 2006

Hi,

I have Pay object with a collection of line items which in turn has line items contained within. Now my question is this (in VB.NET with the 2.03 CSLA) how do I get the totals recalculated when a line item's property changes.

ie. If the hours change etc.

and please feel free to be precise because I have looked a few examples and the theory makes sense but I don't know how to implement it

Thanks

boo replied on Thursday, October 26, 2006

I would imagine you'd want to raise an event when the cost of the line item changes, I'm rusty on my VB so I won't demonstrate the code but basically (and this all in MSDN) your going to want a delegate for the event and the event itself and then just fire it from your child object and your parent object should consume it (have a handler wired up for the event). 

A simpler answer hower (and one that won't show how rusty my VB is) may be the fact that children objects have access to their parent object, so you could simply have a method call (internal or Friend in VB) called CalculateTotal in your parent object, so then in your child object when a total was changed you would call it like:

DirectCast(Parent, Pay).CalucalateTotal()

Not a glamourous solution and I think the event model is a better solution, but this has the KISS principal covered and if your not familiar with creating delegates and events it might be a better solution for you, but then I'm also not sure if this how Parent was intended to be used...

SonOfPirate replied on Friday, October 27, 2006

If all of your objects are CSLA-type BO's, then the child objects (line items) should already expose a PropertyChanged type event that can be handled by the parent collection - and should already be in order to implement IsDirty.  Simply add the call to recalculate your total to the existing event handler.

Or, to save yourself from having to iterate over all of the child items each time in order to recalculate, create a new event, something like AmountChanged with the previous amount and new amount as eventarg parameters.  Then add a handler to each child object as it is added to the collection.  In the event handler, simply calculate the adjustment you need to make to the total by finding the difference in the child item's amount: _total = _total - (e.OldAmount - e.NewAmount).

You can clean it up and make the symantics a bit more appealing but those are two of the easiest ways to go and maintain a more decoupled relationship between the child objects and their parents.

Going with the previous suggestion where you call a method in the parent object from the child directly couples the child objects to their parent meaning that you can only use the LineItem (child object) within your Pay object.  If you wanted to reuse the object elsewhere, you'd have problems.

Hope that helps.

 

Wal972 replied on Friday, October 27, 2006

Conceptuually yes, practically no.

I don't know what the code would be, could you please give me an example ie. Which event handler ?

Thanks

Wal972 replied on Saturday, October 28, 2006

Still needing help desperately thanks

pelinville replied on Saturday, October 28, 2006

Wal972:
Still needing help desperately thanks
 
Anther thing you could do is create another class who totals the lineItems.  This gives you alot a flexability and you don't have to worry about capturing events and what not.

Wal972 replied on Saturday, October 28, 2006

Thanks for the ideas, howver i repeat i don't know what code to write. I have a lineitem collection which has the required calculating code, but i don't know how to activate it when a particular line item changes. CAN SOMEONE PLEASE PROVIDE A SAMPLE CODE OF WHAT IS REQUIRED

UEGENT

THANKS

dean replied on Saturday, October 28, 2006

I define a new event so that we don't run the totalling code on every property change. Otherwise skip step 1 and in your collection trap PropertyHasChanged() event.


1. Define an event in your child property (event is safe for serializing)

#Region " Price Changed event "

    <NonSerialized()> _
    Private mNonSerializableHandlers As EventHandler(Of System.ComponentModel.PropertyChangedEventArgs)
    Private mSerializableHandlers As EventHandler(Of System.ComponentModel.PropertyChangedEventArgs)

    ''' <summary>
    ''' Implements a serialization-safe RemovingItem event.
    ''' </summary>
    Public Custom Event PriceChanged As EventHandler(Of System.ComponentModel.PropertyChangedEventArgs)

        AddHandler(ByVal value As EventHandler(Of System.ComponentModel.PropertyChangedEventArgs))
            If value.Method.IsPublic AndAlso (value.Method.DeclaringType.IsSerializable OrElse value.Method.IsStatic) Then
                mSerializableHandlers = CType(System.Delegate.Combine(mSerializableHandlers, value), EventHandler(Of System.ComponentModel.PropertyChangedEventArgs))
            Else
                mNonSerializableHandlers = CType(System.Delegate.Combine(mNonSerializableHandlers, value), EventHandler(Of System.ComponentModel.PropertyChangedEventArgs))
            End If
        End AddHandler

        RemoveHandler(ByVal value As EventHandler(Of System.ComponentModel.PropertyChangedEventArgs))
            If value.Method.IsPublic AndAlso (value.Method.DeclaringType.IsSerializable OrElse value.Method.IsStatic) Then
                mSerializableHandlers = CType(System.Delegate.Remove(mSerializableHandlers, value), EventHandler(Of System.ComponentModel.PropertyChangedEventArgs))
            Else
                mNonSerializableHandlers = CType(System.Delegate.Remove(mNonSerializableHandlers, value), EventHandler(Of System.ComponentModel.PropertyChangedEventArgs))
            End If
        End RemoveHandler

        RaiseEvent(ByVal sender As System.Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
            If mNonSerializableHandlers IsNot Nothing Then
                mNonSerializableHandlers.Invoke(sender, e)
            End If
            If mSerializableHandlers IsNot Nothing Then
                mSerializableHandlers.Invoke(sender, e)
            End If
        End RaiseEvent
    End Event


#End Region



2. call the event In your child property that changes the totals


    Public Property Amount() As Decimal Implements iInvoiceDetail.Amount
        Get
            Return mTotal
        End Get
        Set(ByVal Value As Decimal)
            If mTotal <> Value Then
                    mTotal = Value
                PropertyHasChanged("Amount")
                RaiseEvent PriceChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Amount"))

            End If
        End Set
    End Property

3.In your collection :

' add a handler to each item
 Protected Overrides Sub OnListChanged(ByVal e As System.ComponentModel.ListChangedEventArgs)
        If e.ListChangedType = ListChangedType.ItemAdded Then
            AddHandler Item(e.NewIndex).PriceChanged, AddressOf OnPriceChanged
        ElseIf e.ListChangedType = ListChangedType.ItemDeleted Then
            OnPriceChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("OrderDetails-ListChanged"))
        End If
        MyBase.OnListChanged(e)
    End Sub

' Define OnPRicechanged

Protected Sub OnPriceChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)

' do your totalling here
    End Sub

Let me know if something isn't working - I can clarify.

Dean


Wal972 replied on Saturday, October 28, 2006

Thanks Dean

Ok I can implement step 1 and 2 but three is confusing me.

I'm using an interface for my line items in the collection its affecting the code help

Thanks

Ellie

dean replied on Saturday, October 28, 2006

in your line item interface declare

Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)

and implement in each line item class. Does that do it?

Dean

Wal972 replied on Saturday, October 28, 2006

Sorry for being so ignorant, but against what do I implement in each line item class

secondly if I need just the quick version for another problem, how do I trap the Propertyhaschanged event in the Collection

Thanks Heaps Dean

Ellie

dean replied on Saturday, October 28, 2006

1.  in each line item class declare declare the code in step 1 from my earlier post but add the implements iLineITem.PriceHasChanged after public custom event PriceChanged... Haven't tried this actually but hopefully it will work - I can test tomorrow.

2. You still need the code from step 3 to add the handler to each line item just change PriceChanged to PropertyChanged.

Dean


Wal972 replied on Sunday, October 29, 2006

The delegate types are different ?

I'm after the changes to an existing line as well new ones will the approach capture that as well

What is the quick way too, because I don't know that either

dean replied on Sunday, October 29, 2006

Sorry - It was a little late last night when I made that last reply.

To use the current PropertyHasChanged event to do the totalling you need to add a handler to each item in the collection. Since this event is already in the line item class you won't need to add any code to the line item class.

' add a handler to each item
 Protected Overrides Sub OnListChanged(ByVal e As System.ComponentModel.ListChangedEventArgs)
        If e.ListChangedType = ListChangedType.ItemAdded Then
            AddHandler Item(e.NewIndex).PropertyHasChanged, AddressOf OnPriceChanged
        ElseIf e.ListChangedType = ListChangedType.ItemDeleted Then
            OnPriceChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("OrderDetails-ListChanged"))
        End If
        MyBase.OnListChanged(e)
    End Sub

And add the OnPriceChanged sub to the collection do the totalling:
' Define OnPRicechanged

Protected Sub OnPriceChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)

' do your totalling here

 end sub

That should do it for using the existing event. When I get a chance I will work on the custom event with items implementing an interface.

Dean

Wal972 replied on Sunday, October 29, 2006

Much appreciated

But it doesn't work when there's the interface inbetween, can you please suggest something and also how I get the root object to listen to the collection listening to the propertychanged

Thanks Dean

Ellie

Wal972 replied on Monday, October 30, 2006

Still needing help badly

Brian Criswell replied on Monday, October 30, 2006

Can you not just listen to the ListChanged event on the list?  The ListChangedEventArgs contains a property for which property of the item changed.

Wal972 replied on Monday, October 30, 2006

True but how and from where, this is new to me, so I need code examples. Also the inteface I have on the children of the collection is acting as inteferance.  I can it work some of the time, like add and delete but not when a change has occured

Thanks

Brian Criswell replied on Monday, October 30, 2006

Your list inherits from BusinessListBase so it will have a ListChanged event.  In your parent object, add an event handler to _myList.ListChanged.  The ListChangedEventArg ("e" by default) will have a property called ListChangedType.  When the ListChangedType is ItemChanged, the PropertyDescriptor property will tell you which property on that item changed.  If the property is the amount property, recalculate the total.  There are hundreds of examples of handling the ListChanged property on the internet.

Wal972 replied on Monday, October 30, 2006

OK with the collection to child problem I can't get a lock on the PropertyChanged event because of the interface i am using to define the collection items. The interface doesn't have the propertychanged event and when I try to get around this it doesn't work. How do I access a child's propertychanged via an interface.

If I add the handler in the list its looking for the same signature as it. I can match that in the interface, but I can't link the interface back to the child propertychanged which is a sub not an event

Help

Also do I need to add a itemchanged as well as add and deleted. Because it does activate on the change only the add and delete.

Brian Criswell replied on Monday, October 30, 2006

Have your interface inherit System.ComponentModel.INotifyPropertyChanged.

Wal972 replied on Monday, October 30, 2006

Thanks Brian I will try this tonight.

Wal972 replied on Tuesday, October 31, 2006

Firstly the interface code resolved one problem but

I have added the following code yet the Calculating Code is not being called WHY ? and I need to move this to the Pay parent as well because it need properties from it. Specific code required for the parent object.  HELP :) Thanks

Protected Overrides Sub OnListChanged(ByVal e As System.ComponentModel.ListChangedEventArgs)

If e.ListChangedType = ComponentModel.ListChangedType.ItemAdded Then

AddHandler Item(e.NewIndex).PropertyChanged, AddressOf OnPayChanged

ElseIf e.ListChangedType = ComponentModel.ListChangedType.ItemChanged Then

AddHandler Item(e.NewIndex).PropertyChanged, AddressOf OnPayChanged

ElseIf e.ListChangedType = ComponentModel.ListChangedType.ItemDeleted Then

OnPayChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("PayDetails-ListChanged"))

End If

MyBase.OnListChanged(e)

End Sub

Protected Sub OnPayChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)

Calculating code

End Sub

Not sure if this is correct please advise

Wal972 replied on Tuesday, October 31, 2006

Still need help, please be patient

Wal972 replied on Wednesday, November 01, 2006

THANKS HEAPS EVERYONE

Its working

demwa replied on Wednesday, October 01, 2008

I have a similar problem,How did you solve yours.I have a parent BO called cheque Requisition which contains the child collection object called chequerequisitionlineitems which in turn contains chequerequisitionlineitem BO.The parent BO has the TotalAmount property which totals the sum of the Amount property of the child BO's.Now when I change the Amount property of the Child BO the Parent BO TotalAmount is not being updated to reflect the changes.Anyone who can help?

sergeyb replied on Wednesday, October 01, 2008

The way I solved this in the past is as follows.

Req object has readonly property Total (sum of child objects in child list amount property).  In the getter of it I would run through collection and compute the sum.  I would also have friend (internal) method RefreshTotal that would only raise PropertyChange event for Total property.

I would carry parent property in child list and from each child’s setter of the amount call this.Parent.Parent.RefreshTotal().  This way you do not need to rely on events and you have explicit call to refresh the total.  Also, if total is not bound to the UI, you will also avoid the overhead of actually computing it.

 

Sergey Barskiy

Principal Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: demwa [mailto:cslanet@lhotka.net]
Sent: Wednesday, October 01, 2008 12:15 PM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] Totals and collections

 

I have a similar problem,How did you solve yours.I have a parent BO called cheque Requisition which contains the child collection object called chequerequisitionlineitems which in turn contains chequerequisitionlineitem BO.The parent BO has the TotalAmount property which totals the sum of the Amount property of the child BO's.Now when I change the Amount property of the Child BO the Parent BO TotalAmount is not being updated to reflect the changes.Anyone who can help?

demwa replied on Thursday, October 02, 2008

Sorry for being so ignorant,plse show me the sample code for refreshTotal.

 

Many thanks

sergeyb replied on Thursday, October 02, 2008

RefreshTotal is very simple – one line of code

OnPropertyChanged("Total")

 

Sergey Barskiy

Principal Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: demwa [mailto:cslanet@lhotka.net]
Sent: Thursday, October 02, 2008 8:07 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] RE: Totals and collections

 

Sorry for being so ignorant,plse show me the sample code for refreshTotal.

 

Many thanks



demwa replied on Sunday, October 05, 2008

Thanks for the help,but I seem to be missing something

Here is the code snipnet in the Cheque Requisition object

Public ReadOnly Property Amount() As String

Get

Return Format(mChequeRequisitionLineItems.Amount, "#,##0.00")

End Get

End Property

Friend Sub RefreshTotal()

OnPropertyChanged("Amount")

End Sub

in the Collection I have the following code

Friend ReadOnly Property Amount() As Decimal

Get

mAmount = 0

For Each child As ChequeRequisitionLineItem In Me

mAmount += child.Amount

Next

Return mAmount

End Get

End Property

In the child object I have the following code

Public Property Amount() As Decimal

Get

CanReadProperty(True)

Return mAmount

End Get

Set(ByVal value As Decimal)

CanWriteProperty(True)

If mAmount <> value Then

mAmount = value

PropertyHasChanged("Amount")

 

End If

End Set

End Property

My problem is I don't to access the me.Parent.Parent.RefreshTotal in the child object.Where I have I gone wrong?

sergeyb replied on Sunday, October 05, 2008

You should have access Parent (child list) in child object.  In child list you will need to add read only property for parent because Parent property in BB and BLB is protected thus not accessible outside of the object.

 

So your list will have

Friend ReadonlYproperty MyParent as Root

Return Ctype(My.Parent, Root)

 

 

Then in child’s Set Amount you will have

Ctype(Me.Parent, ChildList).MyParent.RefreshTotal()

 

Sergey Barskiy

Principal Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: demwa [mailto:cslanet@lhotka.net]
Sent: Sunday, October 05, 2008 5:28 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] RE: Totals and collections

 

Thanks for the help,but I seem to be missing something

Here is the code snipnet in the Cheque Requisition object

Public ReadOnly Property Amount() As String

Get

Return Format(mChequeRequisitionLineItems.Amount, "#,##0.00")

End Get

End Property

Friend Sub RefreshTotal()

OnPropertyChanged("Amount")

End Sub

in the Collection I have the following code

Friend ReadOnly Property Amount() As Decimal

Get

mAmount = 0

For Each child As ChequeRequisitionLineItem In Me

mAmount += child.Amount

Next

Return mAmount

End Get

End Property

In the child object I have the following code

Public Property Amount() As Decimal

Get

CanReadProperty(True)

Return mAmount

End Get

Set(ByVal value As Decimal)

CanWriteProperty(True)

If mAmount <> value Then

mAmount = value

PropertyHasChanged("Amount")

 

End If

End Set

End Property

My problem is I don't to access the me.Parent.Parent.RefreshTotal in the child object.Where I have I gone wrong?



mwaped replied on Tuesday, October 07, 2008

Thanks for being patient with me.I still seem to have the problem when I write the code you proposed   in the BLB

Public readonly property MyParent()as ChequeRequisition

   get

         return cytpe(my.Parent,ChequeRequisition)

   End get

End property

But I am getting a syntax error because the my object does not have the parent propperty/method I have also tried the me.Parent it also does not work.Where am I going wrong.

 

Many thanks

mwaped replied on Sunday, October 12, 2008

Sergey,

I still need your help

sergeyb replied on Sunday, October 12, 2008

Could you post your classes (or at least parts that are pertinent to the solution) if sample below is not clear?  I am not a big fan though of custom events in CSLA.  I would personally prefer direct (explicit) interaction between objects in a graph rather than events based (implicit) interaction.  It does introduce tight coupling between objects in a graph, but technically should not re-use objects in multiple graphs bases on single responsibility principal.  I will let you make a decision which route you would like to go, because I am in a minority I think with my approach on this forum.

 

Here is how I implement my approach in 2.0 (because lists do not have parent property in 2.0, like in 3.6/3.5)

 

Public Class Pay

Friend Sub UpdateTotal

OnPropertyChanged(“Total”)

 

When new and Fetch

_payItemsList.Parent = Me

 

 

Protected Overrides OnDesiralized

MyBase. OnDesiralized

_payItemsList.Parent = Me

 

Public class PayItem

Pretty much the same code as pay to maintain a parent for PayLineItemsList

Friend Sub UpdateTotal

OnPropertyChanged(“Total”)

Ctype(Me.Parent, PayItemList).Parent.UpdateTotal()

 

 

Public class PayItemList

Friend Property Parent as Pay

 

Public Class PayIteLineItem

 

Public Property Total

Ctype(Me.parent, PayIteLineItemList).Parent.UpdateTotal()

 

 

Public Class PayIteLineItemList

 

 

Sergey Barskiy

Principal Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: mwaped [mailto:cslanet@lhotka.net]
Sent: Sunday, October 12, 2008 7:38 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] RE: RE: Totals and collections

 

Sergey,

I still need your help



dean replied on Monday, October 30, 2006

Define doesn't work - compile error or is the sub doing the totalling not getting called?

Does the OnListChanged event get called when you add an item to your collection?

When/if it is called do you see the handler getting added to that item?

Dean


glenntoy replied on Thursday, November 02, 2006

Sharing my solution.. don't know if this is efficient enough...

Ok.. i dont know if I can help you.. but i'll try...
In your child object the line items..

' Declare the event to be fired into the parent object of line items
Friend Event TotalsChanged(ByVal totQty As Integer, ByVal totAmnt As Single)


'actual calcuation of goes here
Protected Overrides Sub OnListChanged(ByVal e As System.ComponentModel.ListChangedEventArgs)

        If e.PropertyDescriptor IsNot Nothing AndAlso _
            (e.PropertyDescriptor.Name = "Qty" OrElse e.PropertyDescriptor.Name = "Price") Then

            Dim qty As Integer = 0, amnt As Single = 0
            For i As Integer = 0 To Me.Count - 1
                qty += Me.Item(i).Qty
                amnt += (Me.Item(i).Qty * Me.Item(i).Price)
            Next i

            RaiseEvent TotalsChanged(qty, amnt)
        End If

        MyBase.OnListChanged(e)
  End Sub


In your parent object where the line items is the child.. declare withEvents for the line items

   Private WithEvents _items As LineItems = Nothing

    Public ReadOnly Property Items() As DeliveryItems
        Get
            If _items Is Nothing Then
                If Me.IsNew Then
                    _items = LineItems.NewLineItems()
                Else
                    _items = InvoiceItems.GetInvoiceItems(Me._invno)
                End If
            End If

            Return _items
        End Get
    End Property

Assigned the total amount and total qty property variables to the events TotalsChanged

Private Sub _items_TotalsChanged(ByVal TotQty As Integer, ByVal TotAmnt As Single) Handles _items.TotalsChanged
        Me._totAmnt = TotAmnt
        OnPropertyChanged("TotAmnt")
        Me._totQty = TotQty
        OnPropertyChanged("TotQty")
    End Sub



Copyright (c) Marimer LLC