Observal
I just released some source on Google Code called Observal:
http://code.google.com/p/observal/
Observal was extracted from work on a recent WPF project. In our application, we had a deep hierarchy of view model objects, with some very complicated interrelationships - setting one property over here means adding or removing items from a collection over there - and since WPF applications are so stateful, we had to do it all reactively.
The project home page gives a simple example of how Observal might be used. I'll use the remainder of this post for a deeper example.
Example
Suppose we have an object model to represent an organization chart:
We'll build a view to show and edit a hierarchy of employees, and provide a filter to show a list of items from the hierarchy:
Working with the hierarchy in WPF is easy - we just build a hierarchical object model and bind it to the tree view. We could build that view model using code like this:
public partial class OrgChartWindow : Window
{
public OrgChartWindow()
{
InitializeComponent();
var sampleEmployees =
new Employee("Ryan Howard", 200000,
new Employee("Michael Scott", 130000,
new Employee("Dwight Schrute", 80000),
new Employee("Jim Halpert", 80000,
new Employee("Andy Bernard", 75000,
new Employee("Stanley Hudson", 70000),
new Employee("Phyllis Lapin", 70000)))));
DataContext = new OrgChartViewModel(new[] { sampleEmployees });
}
}
That gives us the tree view, ability to add new employees and editing support. But how to we manage the list of employees earning under $100,000?
Enter Observal
The "Employees with salary < $100,000" panel is effectively a flattened view of the employee hierarchy. To build it, we'd need to subscribe to the CollectionChanged
event on every employee's DirectReports
collection, and to subscribe to the PropertyChanged
event on every employee.
Observal makes this trivial. We can make the following addition to our view model:
public OrgChartViewModel(IEnumerable<Employee> employees)
{
_rootEmployees = new ObservableCollection<Employee>(employees);
var observer = new Observer();
observer.Extend(new TraverseExtension()).Follow<Employee>(e => e.DirectReports);
observer.Extend(new CollectionExpansionExtension());
observer.Extend(new PropertyChangedExtension()).WhenPropertyChanges<Employee>(x => FilterEmployee(x.Source));
observer.Extend(new ItemsChangedExtension()).WhenAdded<Employee>(FilterEmployee);
observer.Add(_rootEmployees);
}
private void FilterEmployee(Employee employee)
{
if (employee.Salary < 100000)
{
if (!FilteredEmployees.Contains(employee))
FilteredEmployees.Add(employee);
}
else
{
FilteredEmployees.Remove(employee);
}
}
The idea behind observal is that there is an Observer
, which keeps a list of items being observed. Observers can accept IObserverExtensions
, which are notified when items are added or removed. In the example above, we make use of four different extensions:
TraverseExtension
- any time an employee is added to the collection, we'll add theDirectReports
collection too.CollectionExpansionExtension
- when the DirectReports collection is added, we'll add all items in the collection to the observer.PropertyChangedExtension
- this is called any time a property on an existing object changesItemsChangedExtension
- this notifies us whenever an item is added or removed
Each extension is useful by itself, but they become very powerful when combined together. In this example, we were able to monitor an entire hierarchy of objects, and to react whenever parts of the hierarchy changes. I'd urge you to check out Observal on Google Code and let me know what you think.