Bindable LINQ: Bindable Aggregates

As with other LINQ operations, Bindable LINQ allows you to execute aggregates over values that can change over time. In the general sense, a Bindable LINQ aggregate is an operation that turns a series of values into a single value, and is not necessarily limited to numeric operations. Examples of such aggregates are classics such as Sum, Min, Max and Average, as well as operations like First or Contains. You can perform your own simple aggregates using the Aggregate operation, or for more complex custom aggregates you can derive from the Aggregate class and build your own extension method.

Since value types like Decimal can’t actually change, Bindable LINQ wraps the results of these aggregates in a generic interface called IBindable:

public interface IBindable<TValue> : IBindable
{
    TValue Current { get; }
}

User interface code can be bound to the IBindable object’s Current property to reflect the value, as shown in the following XAML:

<TextBlock Text="{Binding Path=Current}" />

An example of such a query would be:

this.DataContext = lineItems.AsBindable().Sum(item => item.Price);

This feature is very powerful when building bindable UI models. For instance, when designing a point-of-sale system, the following class could serve to manage some of the logic behind the the UI:

public class OrderBuilder : INotifyPropertyChanged
{
    private ObservableCollection<LineItem> _scannedLineItems;
    private decimal _discount = 0.0M;
    private IBindable<decimal> _grossPrice;
    private IBindable<decimal> _totalPrice;

    public OrderBuilder()
    {
        _scannedLineItems = new ObservableCollection<LineItem>();
        _grossPrice = _scannedLineItems.AsBindable().Sum(item => item.Price);
        _totalPrice = _grossPrice.Project(gross => gross * (1.00M + this.Discount));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public decimal Discount
    {
        get { return _discount; }
        set
        {
            _discount = value;
            OnPropertyChanged(new PropertyChangedEventArgs("Discount"));
        }
    }

    public IBindable<decimal> GrossPrice
    {
        get { return _grossPrice; }
    }

    public IBindable<decimal> TotalPrice
    {
        get { return _totalPrice; }
    }

    public ObservableCollection<LineItem> ScannedLineItems
    {
        get { return _scannedLineItems; }
    }

    private void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

We now have an object that represents the state of data input and the totals, and might be used as follows:

  • A ListBox is bound to the ScannedLineItems property; when barcodes are entered the line item is created and added to the ScannedLineItems property where it appears in the UI.
  • A TextBlock or Label is bound to the TotalPrice
  • A TextBlock or Label is bound to the GrossPrice
  • A TextBox is bound to the Discount, and a converter is used (to convert "15" into "0.15", as this is a UI-level conversion)

This gives you an object that is encapsulated, can be reused, and is easily testable.

For more information about which aggregate types are supported in Bindable LINQ, check the Compatibility page on the project wiki.

2 Responses to “Bindable LINQ: Bindable Aggregates”

  1. […] Bindable LINQ: Bindable Aggregates (Paul Stovell) […]

  2. This seems to be a great feature with many commmon applications. For the retail example given if your classes, both master and detail implemented a property bool Is_Valid which represented whether each was valid or not could BindableLinq be used to perform a max aggregrate on this property to establish a property which determined wether or not in total the object was valid? Thanks

Leave a Reply