The Scenario goes something like this:
- A data visualization application
- There are literally tens of thousands of “data items”
- The data is stored remotely on a server
- The data items are identified by unique “tags”, which are strings
I want to allow WPF designers to bind to this data by using XAML binding syntax knowing only the tags. The required data item values would be fetched as needed from the server. Only the items currently used in the UI would be fetched.
After many days thinking about this problem and after several failed implementation attempts (most using reflection in one way or another), I stumbled upon what may be an excellent solution this morning. The solution is described in general terms below.
Data Binding to Indexer Properties
The key (no pun intended) is data binding to indexer properties, using a string (the “Tag”) as the index key. The WPF documentation describes this feature, but I must have failed to notice. The indexer is typically an integer, but it can be any object – the “tag” of a data item in this case. Designers can use the {Binding Path=[tag]} XAML syntax (note the use of square brackets), utilizing whatever tags they need.
<StackPanel Orientation="Horizontal">
<TextBlock Text="Value 1:"/>
<TextBlock Text="{Binding Path=[variable1]}" />
</StackPanel>
In the example above, the key “variable1” is specified in the binding path to the text block.
The data context is then set to an object that contains an indexed property using a String type as the indexer key.
public Window1()
{
InitializeComponent();
this.DataContext = new BindingObject();
}
When retrieving data, the Get method for the indexed property will first check its local data cache, returning the data already available. Otherwise, the Get method will access the remote data store to bring it into the cache.
public class BindingObject : INotifyPropertyChanged
{
private PropertyChangedEventArgs _dataChangedEventArgs = new PropertyChangedEventArgs("");
private Dictionary<String, double> _data;
public double this[string index]
{
get
{
if (!_data.ContainsKey(index))
{
double valueFromServer = 0.0;
// ... Fetch data from server
_data.Add(index, valueFromServer);
}
return _data[index];
}
}
// Rest of object ...
Using this technique, there is no need to pull in all of the thousands of data points potentially available – only the ones that are used in binding expressions are retrieved. The indexer syntax also allows a binding path without having to create properties on the data context object for each tag. I can also think of several optimizations that would allow less trips over to the remote data store (maybe these will be discussed in a future post).
What if the data changes?
Occasionally, the values of the data items are updated. How will this implementation support dynamic data updates? The answer could be the INotifyPropertyChanged interface. (As far as I know, an indexed property cannot be a dependency property). I though about having a dependency property which itself contained an indexer property, but I could not figure out how to formulate the XAML binding syntax for that. The question was, what “Property” has changed for the notify event?
To my surprise, raising the PropertyChanged event using String.Empty as the property name works! The WPF binding system must assume this is the indexed property, then dutifully performs updates on ALL of the bound tags (indexer keys) from this single event. This behavior is perfect for my scenario. The remote server will notify me of data update, the local application will update its local cache for all tags currently in use, then raise a single PropertyChanged event on the property named “” and all of the bindings in the UI are updated. Wow.
Two-Way Binding
Two-way binding is not going to be a problem either; just add a setter to the indexed property. When the setter is invoked, a call is made to the remote server to update the data for the indicated tag.
Better ways to do this?
Are there better ways to do this? Probably. I plan to keep looking. My scenario requires good performance and a decent amount of scalability. The remote data can sometimes update quickly (several times per second), and local UIs may contain hundreds of elements bound to specific tags. I need a solution that is bound by either the network or graphics re-painting – the binding infrastructure should be negligible. I plan to perform some scalability tests using this technique to see if the requirements for my scenario are met. If anyone knows some answers, I'm all ears.