Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reactive Extensions / Reactive UI Observe collection, group it, and display it

I have a specific data set that requires storing data that is both grouped and ordered. Each row then needs to be able to recalculate it's sums whenever any individual item changes.

This is the basic look of the end result:

enter image description here

The object structure is as such:

public class MyObject : INotifyPropertyChanged
{
  public ObservableCollection<MySubObject> Objects { get; set; }
  public Decimal TotalOfAll { get; set; }

  /* Ommited INPC and other properties for brevity */
}

public class MySubObject : INotifyPropertyChanged
{
  public Decimal Value { get; set; }
  public String RowType { get; set; }

  /* Ommited INPC and other properties for brevity */
}

The view needs to bind to MyObject, then group the Objects property by it's Type property.

Now, I've already accomplished this without using reactive extensions, but it feels hackish...I would like to accomplish this by converting the Objects property of MyObject to an observable which should, in theory, allow me to update the sums whenever the Value property of MySubObject changes.

I already have the view side of things built, so that's not the issue...it's getting the RX part completed.

NOTE:

I can alternatively expose my data like this:

public class MyObject : INotifyPropertyChanged
{
  public ObservableCollection<MyRowObject> Objects { get; set; }
  public Decimal TotalOfAll { get; set; }

  /* Ommited INPC and other properties for brevity */
}

public class MyRowObject : INotifyPropertyChanged
{
  public ObservableCollection<MySubObject> Objects { get; set; }
  public String RowType { get; set; }
  public Decimal RowTotal { get; set; }

  /* Ommited INPC and other properties for brevity */
}

public class MySubObject : INotifyPropertyChanged
{
  public Decimal Value { get; set; }

  /* Ommited INPC and other properties for brevity */
}

This would take care of the grouping issue, but I still cannot get it to work

like image 956
Robert Petz Avatar asked Aug 14 '13 18:08

Robert Petz


1 Answers

You can use ReactiveUI's reactive collection, which provides a number of observables including one for when the collection changes (Changed), and one for when the items in the collection change (ItemChanged). This can be combined with ObservableAsPropertyHelper for the dependent properties.

The totalOfAll dependent property is quite straightforward. The other dependent property - groupedObjects - is a bit more complex. I'm not sure if I've understood your grouping requirements exactly. Hopefully the code below will be a starting point - it will project the collection of subobjects into an IEnumerable of anonymous objects, each containing a total, a row header, and the items. You should be able to bind to this in your view

public class MyObject : ReactiveObject
{
    public ReactiveCollection<MySubObject> Objects { get; set; }

    private ObservableAsPropertyHelper<IEnumerable<object>> groupedObjectsHelper;
    public IEnumerable<object> GroupedObjects
    { 
        get {return groupedObjectsHelper.Value;}
    }

    private ObservableAsPropertyHelper<Decimal> totalOfAllHelper;
    public Decimal TotalOfAll 
    {
        get {return totalOfAllHelper.Value;}
    }

    public MyObject()
    {
         var whenObjectsChange=
           Observable.Merge(Objects.Changed.Select(_ => Unit.Default),
                            Objects.ItemChanged.Select(_ => Unit.Default));

         totalOfAllHelper=
           whenObjectsChange.Select(_=>Objects.Sum(o => o.Value))
                            .ToProperty(this, t => t.TotalOfAll);

         groupedObjectsHelper=
           whenObjectsChange.Select(_=>Objects
                              .GroupBy(o => o.RowType)
                              .Select(g => new {RowType=g.Key,
                                                RowTotal=g.Sum(o => o.Value),
                                                Objects=g}))
                            .ToProperty(this, t => t.GroupedObjects);

    }
}
like image 104
Daniel Neal Avatar answered Oct 19 '22 18:10

Daniel Neal