Sunday, October 18, 2009

Live Expressions With Obtics

I'm busy working on a WPF app that's replacing a Excel spreadsheet solution. It involves lots of summary stats (sums, averages etc.) against a set of items. As the items are editted the changes need to be detected and the stats have to be updated so that the user can see the affects of his changes.

The normal way of doing this is to make the items implement INotifyPropertyChanged and then listen to changed in the properties that the stats are dependent on. As the stats get more complicated this gets more and more complicated and error prone.

Obtics is a library that makes this much easier.

Take the following example:

public class Portfolio
{
  public Portfolio()
  {
    Holdings = new ObservableCollection<Holding>();
  }

  public ObservableCollection<Holding> Holdings { get; private set; }
}

public sealed class Holding : INotifyPropertyChanged
{
  private int quantity;
  private Stock stock;

  public int Quantity
  {
    get 
    {
      return quantity;
    }
    set
    {
      if (quantity == value) 
      {
        return;
      }
      quantity = value;
      OnPropertyChanged("Quantity");
    }
  }

  public Stock Stock
  {
    get
    {
      return stock;
    }
    set
    {
      if (stock == value) 
      {
        return;
      }
      stock = value;
      OnPropertyChanged("Stock");
    }
  }  

  private void OnPropertyChanged(string propertyName)
  {
    var handler = PropertyChanged;
    if (handler != null) 
    {
      handler(this, new PropertyChangedEventArgs(propertyName));
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

public sealed class Stock : INotifyPropertyChanged
{
  private decimal price;

  public decimal Price
  {
    get
    {
      return price;
    }
    set
    {
      if (price == value) 
      {
        return;
      }
      price = value;
      OnPropertyChanged("Price");
    }
  }  

  private void OnPropertyChanged(string propertyName)
  {
    var handler = PropertyChanged;
    if (handler != null) 
    {
      handler(this, new PropertyChangedEventArgs(propertyName));
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

If I want the value of the portfolio I can write this:

var portfolioValue = Obtics.Values.ExpressionObserver.Execute(() => porfolio.Holdings.Sum( x => x.Quantity * x.Stock.Price));

portfolioValue is of the type IValueProvider and portfolioValue.Value will give the current value of expression. The real magic is that Obtics analyzes the expression and automatically refreshes the value when the inputs change. In this example, adding and removing holdings, changing the holding quantities and even the stock prices will cause the value to update. If you did that by listening to the PropertyChanged events, it would take 10 times the amounts of code.

portfolioValue can also be cast to INotifyPropertyChanged, so porfolioValue.Value can be bound in WPF.

4 comments:

Ryan said...

Henri -

We are using Obtics, too. However - we have discovered a problem with Obtics truncating our decimal values.

Could you please try these 2 quick tests for us? We would GREATLY appreciate it:

1.) Does portfolioValue.Value return a decimal or just an integer?

2.) Try increasing your a Stock Price by .10 cents (anything less than 1)... does portfolioValue detect the change?

We can't get Obtics to return a decimal to us, it's truncating to an integer. It also won't detect any update less than 1 for us.

We are very anxious to see if you have gotten these two tests to work.

Please us know if your example here is working properly!

Much appreciated! Thank you, Henri!

Ryan D. Hatch (& Jeremiah Redekop)

Jeremiah said...
This comment has been removed by the author.
Jeremiah said...

Maybe we're missing something obvious, but here is our test:

Public Sub TestCalcSimple()
Dim stuff As IList(Of Double) = New Double() {0.05R, 1R}
Dim result As Double = Obtics.Values.ExpressionObserver.Execute(Function() stuff.Sum()).Value
' WORKS: will be 1.05

Dim decStuff As IList(Of Decimal) = New Decimal() {0.05D, 1D}
Dim decresult As Double = CDbl(Obtics.Values.ExpressionObserver.Execute(Function() decStuff.Sum()).Value)
'DOESN'T WORK: will be 1

Assert.AreEqual(result, decresult)
End Sub

hwiechers said...

It's a bug in Obtics. I voted up the issue you created on the Obtics codeplex site. I hope it gets fixed soon.