Calculate SubTotal for InvoiceDetail and Total for Invoice

Calculate SubTotal for InvoiceDetail and Total for Invoice

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


alef posted on Wednesday, March 04, 2009

I have a classis example.
An invoice with invoicedetails.

An InvoiceDetail has the following properties:
Quantity
UnitPrice
SubTotal

An invoice has the property Total


On the UI the user can add and change InvoiceDetails via a grid.
When a user is changing the Quantity or UnitPrice, the property SubTotal must be calculated automatically. Also the Total of the Invoice must be calculated automatically.  
Below you can find the way I implemented it, but I was wondering it is the good way?


    public partial class InvoiceDetail : Csla.BusinessBase<InvoiceDetail>
    {

        #region Business Properties and Methods
        //register properties
        private static PropertyInfo<decimal> QuantityProperty = RegisterProperty<decimal>(typeof(InvoiceDetail), new PropertyInfo<decimal>("Quantity"));

        private static PropertyInfo<decimal> UnitPriceProperty = RegisterProperty<decimal>(typeof(InvoiceDetail), new PropertyInfo<decimal>("UnitPrice"));

    private static PropertyInfo<decimal> SubTotalProperty = RegisterProperty<decimal>(typeof(InvoiceDetail), new PropertyInfo<decimal>("SubTotal"));
    
        public decimal Quantity
        {
            get { return GetProperty<decimal>(QuantityProperty); }
      set { SetProperty<decimal>(QuantityProperty, value); RecalcSubTotal(); }
        }
 
        public decimal UnitPrice
        {
            get { return GetProperty<decimal>(UnitPriceProperty); }
      set { SetProperty<decimal>(UnitPriceProperty, value); RecalcSubTotal(); }
        }

    public decimal SubTotal
    {
      get { return GetProperty<decimal>(SubTotalProperty); }
      internal set { SetProperty<decimal>(SubTotalProperty, value); OnSubTotalChanged("SubTotal"); }
    }

    private void RecalcSubTotal()
    {
      SubTotal=  Quantity * UnitPrice;
    }
    #endregion


    #region " SubTotal Changed event "
    [NonSerialized()]
    private EventHandler<System.ComponentModel.PropertyChangedEventArgs> mNonSerializableHandlers;
    private EventHandler<System.ComponentModel.PropertyChangedEventArgs> mSerializableHandlers;
    /// <summary>
    /// Implements a serialization-safe RemovingItem event.
    /// </summary>
    public event EventHandler<PropertyChangedEventArgs> SubTotalChanged
    {
        add
        {
            if (value.Method.IsPublic && (value.Method.DeclaringType.IsSerializable || value.Method.IsStatic))
            {
                mSerializableHandlers = (EventHandler<PropertyChangedEventArgs>)System.Delegate.Combine(mSerializableHandlers, value);
            }
            else
            {
                mNonSerializableHandlers = (EventHandler<PropertyChangedEventArgs>)System.Delegate.Combine(mNonSerializableHandlers, value);
            }
        }
        remove
        {
          
            if (value.Method.IsPublic && (value.Method.DeclaringType.IsSerializable || value.Method.IsStatic))
            {
                mSerializableHandlers = (EventHandler<PropertyChangedEventArgs>)System.Delegate.Remove(mSerializableHandlers, value);
            }
            else
            {
                mNonSerializableHandlers = (EventHandler<PropertyChangedEventArgs>)System.Delegate.Remove(mNonSerializableHandlers, value);
            }
        }
     }

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void OnSubTotalChanged(string propertyName)
    {
      if (mNonSerializableHandlers != null)
        mNonSerializableHandlers.Invoke(this,
        new PropertyChangedEventArgs(propertyName));
      if (mSerializableHandlers != null)
        mSerializableHandlers.Invoke(this,
        new PropertyChangedEventArgs(propertyName));
    }

    #endregion
    }
----------------------------------------------------------------

    public partial class InvoiceDetails : Csla.BusinessListBase<InvoiceDetails, InvoiceDetail>
    {
   

    protected override void OnListChanged(System.ComponentModel.ListChangedEventArgs e)
    {
        if (e.ListChangedType == ListChangedType.ItemAdded)
        {
          this[e.NewIndex].SubTotalChanged += new EventHandler<PropertyChangedEventArgs>(InvoiceDetails_SubTotalChanged);
        }
        else if (e.ListChangedType == ListChangedType.ItemDeleted)
        {
          InvoiceDetails_TotalChanged(this, new System.ComponentModel.PropertyChangedEventArgs("InvoiceDetails-ListChanged"));
        }
        base.OnListChanged(e);
    }


    void InvoiceDetails_SubTotalChanged(object sender, PropertyChangedEventArgs e)
    {
      decimal total = 0;
      foreach (InvoiceDetail tmp in this)
      {
        total += tmp.SubTotal;

      }
      ((Invoice)this.Parent).Total =  total;
    }


----------------------------------------------------------------
    [Serializable()]
    public partial class Invoice : Csla.BusinessBase<Invoice>
    {
        #region Business Properties and Methods
        //register properties
    private static PropertyInfo<decimal> TotalProperty = RegisterProperty<decimal>(typeof(Invoice), new PropertyInfo<decimal>("Total"));
   
    public decimal Total
    {
      get { return GetProperty<decimal>(TotalProperty); }
      internal set { SetProperty<decimal>(TotalProperty, value); }
    }

    }



RockfordLhotka replied on Wednesday, March 04, 2009

You can probably use the ChildChanged property (3.5 or later) to simplify the code so you don't need to declare/raise your own events.

alef replied on Friday, March 06, 2009

Thanks for your reply.
The only issue with listening to ChildChanged is that it will fire for all changes to any property of any child no?  I am only interested in the property SubTotal.

Antonio Sandoval replied on Friday, March 06, 2009

Why dont use e.PropertyChangedArgs.PropertyName in the event ChildChanged, so you can raise your SubtotalChanged only when the propertyName correspond to the interested.

swith (e.PropertyChangedArgs.PropertyName){
 case "Quantity":
 case "UnitPrice":
     ((InvoiceDetail)e.ChildObject).RecalcSubtotal();
}

alef replied on Saturday, March 07, 2009

I've tried your idea but apparently OnChildChanged gets only called when a new InvoiceDetail is added.

Afterwards when we change properties of InvoiceDetail, OnChildChanged is not called anymore on the Invoice object.

I'm using the latest version of CSLA (cslacs-3.6.2-090302). Could this be a bug?

 

public partial class Invoice : Csla.BusinessBase<Invoice>

{

protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)

{

if (e.PropertyChangedArgs != null)

{

switch (e.PropertyChangedArgs.PropertyName)

   {

      case "Quantity":

      case "UnitPrice":

      decimal total = 0;

      foreach (InvoiceDetail tmp in this.InvoiceDetails)

      {

            total += tmp.SubTotal;

      }

      Total = total;

      break;

   }

}

base.OnChildChanged(e);

}

}

alef replied on Saturday, March 07, 2009

It is my fault.

In the class InvoiceDetails I had the following code (an empty OnChildChanged method, without calling

base.OnChildChanged(e);) ==> this is the reason why the root object Invoice didn't receive the methoad call OnChildChanged.

public partial class InvoiceDetails : Csla.BusinessListBase<InvoiceDetails, InvoiceDetail>

{

protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)

{

}

....

}

 

Below follows the code which also takes into account a newly added InvoiceDetail and a deleted InvoiceDetail.

In these cases e.PropertyChangedArgs is null and we need to call RecalcTotal also.

public partial class Invoice : Csla.BusinessBase<Invoice>

{

protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)

{

if (e.PropertyChangedArgs == null)

{

RecalcTotal();

}

else

{

switch (e.PropertyChangedArgs.PropertyName)

{

case "SubTotal":

RecalcTotal();

break;

}

}

base.OnChildChanged(e);

}

private void RecalcTotal()

{

decimal total = 0;

foreach (InvoiceDetail tmp in this.InvoiceDetails)

{

total += tmp.SubTotal;

}

Total = total;

}

}

 

But when implementing this the binding of the grid behaves very strange. When adding a new line (InvoiceDetail) to the grid, the focus of the grid goes always to the first row. When putting the code "Total = total; " in the method RecalcTotal in comment the binding behaves correctly. What can be the reason?

 

 

Antonio Sandoval replied on Saturday, March 07, 2009

I'm having a similar  problem, it seems to be a bug, because when I call OnPropertyChanged inside a ListChanged Event the new pending row is automatically commited into the binding list (in your case I dont know if PropertyChanged is called into set_Total, I has been fighting with this all the week, but I have does not found a solution =(.

I'm doing the same that you but in a little different way, I was testing in Csla 3.6.1, this example is a mix hehe, because I have started with Csla 3.6.1 in this week:

public class Invoice {
            private static PropertyInfo<decimal> TotalProperty = RegisterProperty<decimal>(typeof(inventory), new PropertyInfo<decimal>("Total"));
        private InvoiceDetailList _invoiceDetailList= InvoiceDetailList.NewInvoiceDetailList();
        public decimal Total
        {
            get { return GetProperty<decimal>(TotalProperty); }
            internal set { SetProperty<decimal>(TotalProperty, value); }
        }

            private Invoice()
            {
                _invoiceID= NewID();
                _invoiceDetailList.ChildChanged += new EventHandler<Csla.Core.ChildChangedEventArgs>(invoiceDetailList_ChildChanged);                           
            }

            void _invoiceDetailList_ChildChanged(object sender, Csla.Core.ChildChangedEventArgs e)
            {
                   decimal localTotal=0;
                   switch(e.PropertyChangedArgs.PropertyName){
                         case "Quantity":
                         case "UnitCost":
                         case "Discount":
               foreach(InvoiceDetail tmp in _invoiceDetailList){
                       localTotal += tmp.Subtotal;
                                }
                               decimal beforeTotal=Total;
                             if(beforeTotal!= localTotal) {
                                     Total = localTotal; 
                                     OnTotalChanged(this, new TotalChangedEventArgs(beforeTotal,localTotal)); //Only for use in the UI
                              }
                               break;
                       }
            }
}

public class InvoiceDetail {
        public decimal Subtotal {
              get {
                    return Quantity * UnitCost *(1-Discount/100);
                }
        }
}

Something like this I tested in the morning and appear to works.

EDIT 1: dont forget, hehe I have seen this in another post!
protected override void OnDeserialized(System.Runtime.Serialization.StreamingContext context)

    {

      base.OnDeserialized(context);

      _invoiceDetailList.ChildChanged += _invoiceDetailList.ChildChanged(....);

    }

alef replied on Tuesday, March 10, 2009

When calculating the Total property, PropertyChanged will be called automatically by the CSLA framework. The SetProperty will call this PropertyChanged.

public decimal Total

{

get { return GetProperty<decimal>(TotalProperty); }

internal set { SetProperty<decimal>(TotalProperty, value); }

}

I have the same behavior :  new pending row is automatically commited.

So when I'm adding a new row on the InvoiceDetail grid, and I'm filling in the Quantity or UnitPrice property, when I leave the cell the row is automatically comitted (even worse the newly added row loses its focus and the first line in the grid gets the focus).

OnChildChanged will be called on the invoice level when adding a new InvoiceDetail and because of the calculating of the property Total on the Invoice object, also SetProperty will be called which calls also propertychanged, apparently the InvoiceDetail gets committed and loses the focus.

Antonio Sandoval replied on Tuesday, March 10, 2009

This is the same problem that I have :-(, rocky answered to my post saying that this is a bug that was fixed in 3.0.3, but I'm not sure that is the same bug that he means.
I think that it happens because the list is bound to a Datamember of the parent, so ApplyEdit is called automatically by the parent when a property change in the root, and maybe this is not a bug of CSLA, if not that is the way on wich data binding work (I'm suposing, I dont know Confused [*-)] somebody help me please!).
Maybe if instead of use SetProperty:

private decimal _Total=0;
public decimal Total {
 get{
   return _Total;
 }
 set{
   if(_Total!=value){
       _Total=value;
       PropertyHasChanged("Total");
   }
 }
}

if(_Total!=total){
 MarkDirty(false);
 _Total = total;
 ValidationRules.CheckRules();
}

On this way you avoid to raise PropertyChanged.

Best Regards.

alef replied on Friday, March 13, 2009

The call should be MarkDirty(true); in place of MarkDirty(false)
Only in this way you avoid the raise of the event PropertyChanged.
But when doing this the UI doesn't get notified that it has to refresh the Total label.

I've found the following interesting forum :http://forums.lhotka.net/forums/thread/24830.aspx

Especially the following said by Lhotka is the problem we have:

People constantly implement event cascading – where a change in a child object (or list) results in a changed event at the parent level.

That turns out to be bad. Very bad. Having the parent raise a PropertyChanged event when its child list changes causes Windows Forms data binding to react badly. From what I can see, it causes the currency to “flicker” – change just long enough to force a commit of the current row in the grid.

The solution of Lhotka :
So this is unfortunate. It means that BusinessBase can’t bubble a child list’s ListChanged event up as a PropertyChanged event.
I’m thinking that what I’ll do as an alternative, is have a virtual OnChildChanged() method in BusinessBase that gets called when a child’s ListChanged or PropertyChanged event comes in. That will allow notification to the parent that its child has changed. But I won’t continue to raise a PropertyChanged event because that’s obviously bad.

This explanation is really interesting. So indeed Lhotka solved this by not raising anymore the PropertyChanged event. So we have to override the
OnChildChanged() method and that is what I'm doing in the Invoice object.

    public partial class Invoice : Csla.BusinessBase<Invoice>
    {
    protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)
    {
      if (e.PropertyChangedArgs == null)
      {
        RecalcTotal();
      }
      else
      {
        switch (e.PropertyChangedArgs.PropertyName)
        {
          case "SubTotal":
            RecalcTotal();
            break;
        }
      }
      base.OnChildChanged(e);
    }

    private void RecalcTotal()
    {
      decimal total = 0;
      foreach (InvoiceDetail tmp in this.InvoiceDetails)
      {
        total += tmp.SubTotal;

      }
      Total = total;
    }
    public decimal Total
    {
      get { return GetProperty<decimal>(TotalProperty); }
      internal set { SetProperty<decimal>(TotalProperty, value); }
    }

    }


But finally when setting the Total property, the CSLA framework will call anyway the PropertyChanged event and gives us again the problem with the binding.

Lhotka: do you have an idea how to solve this?

alef replied on Tuesday, March 17, 2009

I've opened a case with devexpress http://www.devexpress.com/Support/Center/p/B134492.aspx

You can find there also an example I've created.

They answered the following:

Your approach would work, if changing a property fired a single PropertyChanged notification. But OnChildChanged() / RecalcTotal() also raises a ListChanged event with the ListChangedType.Reset parameter. "Reset" stands for major changes made to the list, and the grid is completely refreshed. The XtraGrid's behavior is qualified as by design.

 

Is there a way to avoid raising a ListChanged event with the ListChangedType.Reset parameter?

 

 

 

tetranz replied on Tuesday, March 17, 2009

alef:
Is there a way to avoid raising a ListChanged event with the ListChangedType.Reset parameter?

I think not. Any property change on the root object will cause a ListChanged Reset on the child list. That's unfortunately how WinForms binding works. It doesn't make much sense but changing one property causes all properties to refresh. For a property which is a list, "refresh" means to send a ListChanged reset.  That means that even if you change one unrelated checkbox on the root, all lists refresh.

Maybe the way to go is to make your subtotal property just a regular property, not a CSLA property. Then it won't fire PropertyChanged. Then you'll need something to make refresh the UI. You can probably do that from the bindingSource CurrentItemChanged event.

alef replied on Friday, March 20, 2009

In the meantime the discussion is going also further on the forum of DevExpress:

http://www.devexpress.com/Support/Center/p/B134492.aspx

Does somebody has some new ideas how to solve this?

alef replied on Monday, March 23, 2009

I've discovered something new.

So in the example I've made (in the link http://www.devexpress.com/Support/Center/p/B134492.aspx you'll find the attachment ProblemPropertyChanged.zip) you'll find two BindingSource controls.

invoiceBindingSource with the following properties

   DataSource: InvoiceLibrary.Invoice

invoiceDetailsBindingSource with the following properties

   DataSource: invoiceBindingSource

   DataMember: InvoiceDetails

In this case the grid doesn't behave correctly. The problem is that when adding a new line and filling in Quantity and afterwards UnitPrice in the XtraGrid, the new item row loses its focus. So the user doesn't have anymore the capability to undo his changes.

When I put the following properties on invoiceDetailsBindingSource

   DataSource: InvoiceLibrary.Invoice

   DataMember: InvoiceDetails

the grid behaves correctly.

So when using one BindingSource as the data source for another BindingSource, the grid doesn't behave well.

But in the example ProjectTracker, that is the way Lhotka is using it. Is there some explanation for this?

RockfordLhotka replied on Monday, March 23, 2009

I created the ProjectTracker UI by using drag-and-drop data binding from the Data Sources window in Visual Studio. My goal was (and is) to use the built-in data binding and designer capabilities provided by VS.

 

Some third party controls may not work perfectly well with the VS designer’s approach to doing data binding. In that case, you may need to do some manual work to set the binding properties on your controls, instead of using the drag-and-drop capabilities of VS.

 

Rocky

Copyright (c) Marimer LLC