Binding to a 2D-array in WPF

WPF’s binding feature is really great to create user interfaces. However, sometimes it has its boundaries. Binding to multi-dimensional arrays is one of those. In this post I will show an alternate approach how to make the binding work.

Binding a UI-control to a one-dimensional array is quite simple. The following code will display the fifth element (index 4) of the array MyOneDimensionalArray of the active DataContext.

<TextBlock Text="{Binding MyOneDimensionalArray[4]}"/>

But as soon as you try out multi-dimensional arrays, the binding won’t work anymore:

<!-- This does not work -->
<TextBlock Text="{Binding MyTwoDimensionalArray[2, 6]}"/>

So if we want to use data binding, we have to use a single index. Fortunately, we are not obliged to use a numeric index. We can encode the index information in a string and decode that string when accessing the element. In order to do so, I created this tiny class that does all this:

/// <summary>
/// This class is a bindable encapsulation of a 2D array.
/// </summary>
/// <typeparam name="T"></typeparam>
public class BindableTwoDArray<T> : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void Notify(string property)
    {
        var pc = PropertyChanged;
        if (pc != null)
            pc(this, new PropertyChangedEventArgs(property));
    }

    T[,] data;

    public T this[int c1, int c2]
    {
        get { return data[c1, c2]; }
        set
        {
            data[c1, c2] = value;
            Notify(Binding.IndexerName);
        }
    }

    public string GetStringIndex(int c1, int c2)
    {
        return c1.ToString() + "-" + c2.ToString();
    }

    private void SplitIndex(string index, out int c1, out int c2)
    {
        var parts = index.Split('-');
        if (parts.Length != 2)
            throw new ArgumentException("The provided index is not valid");

        c1 = int.Parse(parts[0]);
        c2 = int.Parse(parts[1]);
    }

    public T this[string index]
    {
        get 
        {
            int c1, c2;
            SplitIndex(index, out c1, out c2);
            return data[c1, c2]; 
        }
        set
        {
            int c1, c2;
            SplitIndex(index, out c1, out c2);
            data[c1, c2] = value;
            Notify(Binding.IndexerName);
        }
    }

    public BindableTwoDArray(int size1, int size2)
    {
        data = new T[size1, size2];
    }

    public static implicit operator T[,](BindableTwoDArray<T> a)
    {
        return a.data;
    }
}

As you can see, this array has a single two-dimensional array as its data source, yet it has two indexers. One that takes two ints (this is the one you would be using in code) and one that takes a string index (for data binding). The string index consists of both numeric indices, separated by a -. We could easily use the , to separate the indices. However, in this case, the comma would have to be escaped in XAML. Therefore, the minus char seems to be more user friendly.

If performance is critical, you might consider using a numeric flattened index instead of the string index. I guess that the string operations are a bit slower than unflattening the numeric index. But this guess would need some testing.

One drawback of this approach is the INotifyPropertyChanged interface. It allows only to notify of Item[] which means that every element has been changed. This is not optimal for performance.

You can specify a binding as follows:

<TextBlock Text="{Binding MyBindableTwoDArray[2-5]}"/>

Or in code:

txt.SetBinding(TextBlock.TextProperty, new Binding("MyBindable2DArray[" + myBindable2DArray.GetStringIndex(2, 5) + "]"));

The implicit cast operator allows to use the Bindable2DArray in place of any 2D-array. However, be aware that the internal data structure is passed along. Any changes to the returned array will be reflected in the Bindable2DArray but those changes will not propagate to the UI. You may change this behaviour as you need. Furthermore, conversion operators from 2d-arrays to Bindable2DArray are possible.

A small example application can be found here.

Advertisements

, , , , ,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: