Note: This blog post is written using Silverlight 4.0 RC 1
One of the cool new features in Silverlight 4 is the ability to data bind to indexed properties. This means that even if you don’t know at design time what properties you data object has you can still data bind to them.
The syntax is very similar to a normal data binding, only in this case you need to use the [key] syntax instead. For example in example below the FirstName is a regular property while the LastName below is an indexed property.
<StackPanel Name="LayoutRoot">
<StackPanel Orientation="Horizontal" Margin="1">
<TextBlock Text="FirstName" Width="150"></TextBlock>
<TextBox Text="{Binding FirstName, Mode=TwoWay}" Width="500"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="1">
<TextBlock Text="LastName" Width="150"></TextBlock>
<TextBox Text="{Binding [LastName], Mode=TwoWay}" Width="500"></TextBox>
</StackPanel>
</StackPanel>
Creating the class to data bind to is simple. All you need to do is add a property with the following syntax:
public object this[string key]
{ get; set; }
In this example I am using a Peron class with a regular FirstName property and all others are dong using indexed properties. The complete class, including INotifyPropertyChanged looks like this:
public class Person : INotifyPropertyChanged
{
public Person()
{
PropertyChanged = (s, e) => { };
FirstName = "Maurice";
this["LastName"] = "de Beijer";
this["BabyName"] = "Kai";
}
private Dictionary<string, object> _data = new Dictionary<string, object>();
public object this[string key]
{
get
{
if (!_data.ContainsKey(key))
_data[key] = null;
return _data[key];
}
set
{
_data[key] = value;
PropertyChanged(this, new PropertyChangedEventArgs(""));
}
}
public IEnumerable<string> Keys
{
get
{
return _data.Keys;
}
}
private string _firstName;
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
PropertyChanged(this, new PropertyChangedEventArgs("FirstName"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
While not having to know the objects structure at design time is nice but in the XAML above I still hard coded the data bindings so that doesn’t buy us much yet. So fully utilize this we need to dynamically generate the UI as well. Fortunately that is quite easy to do with the following code:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var person = new Person();
DataContext = person;
foreach (var item in person.Keys)
{
var lbl = new TextBlock();
lbl.Text = item;
lbl.Width = 150;
var txt = new TextBox();
var binding = new Binding("[" + item + "]");
binding.Mode = BindingMode.TwoWay;
txt.SetBinding(TextBox.TextProperty, binding);
txt.Width = 500;
var line = new StackPanel();
line.Orientation = Orientation.Horizontal;
line.Children.Add(lbl);
line.Children.Add(txt);
line.Margin = new Thickness(1);
LayoutRoot.Children.Add(line);
}
}
And the result looks like this. The LastName is printed twice because it is both hard coded into the XAML and added dynamically.
A few gotchas I ran into.
While doing this I experimented with using a Dictionary<string, object> as the DataContext itself. That worked file except for the part that this doesn’t implement INotifyPropertyChanged. So i tried to create a Peron class deriving from Dictionary<string, object> and adding a indexer to that but this doesn’t work, data binding completely fails to load the data. The same was try for my own implementation of IDictionary<string, object>.
Another thing was the property name that is used with the INotifyPropertyChanged.PropertyChanged event. It seems using an empty string as the property name is the only thing that works to get other controls to update their binding. Not a big issue but I caught me at first.
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu