views:

6340

answers:

3

I have a 2-dimensional array of objects and I basically want to databind each one to a cell in a WPF grid. Currently I have this working but I am doing most of it procedurally. I create the correct number of row and column definitions, then I loop through the cells and create the controls and set up the correct bindings for each one.

At a minimum I would like to be able to use a template to specify the controls and bindings in xaml. Ideally I would like to get rid of the procedural code and just do it all with databinding, but I'm not sure that's possible.

Here is the code I am currently using:

public void BindGrid()
{
    m_Grid.Children.Clear();
    m_Grid.ColumnDefinitions.Clear();
    m_Grid.RowDefinitions.Clear();

    for (int x = 0; x < MefGrid.Width; x++)
    {
        m_Grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), });
    }

    for (int y = 0; y < MefGrid.Height; y++)
    {
        m_Grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), });
    }

    for (int x = 0; x < MefGrid.Width; x++)
    {
        for (int y = 0; y < MefGrid.Height; y++)
        {
            Cell cell = (Cell)MefGrid[x, y];                    

            SolidColorBrush brush = new SolidColorBrush();

            var binding = new Binding("On");
            binding.Converter = new BoolColorConverter();
            binding.Mode = BindingMode.OneWay;

            BindingOperations.SetBinding(brush, SolidColorBrush.ColorProperty, binding);

            var rect = new Rectangle();
            rect.DataContext = cell;
            rect.Fill = brush;
            rect.SetValue(Grid.RowProperty, y);
            rect.SetValue(Grid.ColumnProperty, x);
            m_Grid.Children.Add(rect);
        }
    }

}
+10  A: 
Jobi Joy
I don't necessarily need to use a grid for the databinding, but I don't want to have to create a list of lists for the source. I want to use an object that has an indexer that takes two parameters, x and y.
Daniel Plaisted
WPF binding cant recongnize an array, it has to be an Enumerable collection. So better create a List of List and databind it.
Jobi Joy
Is it possible to use the new WPF DataGrid to achieve this?
Brian
This is pretty much a layout problem dataGrid will be too much for this. You can see that there is no Header needed here like DataGrid has.
Jobi Joy
But what if you did want to use the features of the DataGrid like selection, navigation, etc. How would you put a 2D array into a grid?
AKoran
A: 

Hi. You may want to check out this link: http://www.thinkbottomup.com.au/site/blog/Game%5Fof%5FLife%5Fin%5FXAML%5FWPF%5Fusing%5Fembedded%5FPython

If you use a List within a List you can use myList[x][y] to access a cell.

Torsten
+4  A: 

Update

I made an easy-to-use Control called DataGrid2D that can be populated based on 2-dimensional or
1-dimensional arrays (or anything that implements the IList interface), so I thought I'd share if anyone's interested. DataGrid2D subclasses DataGrid and adds the ItemsSource2D Dependency Property which is used for binding against 2D or 1D sources. The library can be downloaded from here.

To use it just add a reference to DataGrid2DLibrary.dll, add this namespace

xmlns:dg2d="clr-namespace:DataGrid2DLibrary;assembly=DataGrid2DLibrary"

and then create a DataGrid2D and bind it to your IList, 2D array or 1D array like this

<dg2d:DataGrid2D Name="c_dataGrid2D"
                 UseModifiedDataGridStyle="True"
                 ItemsSource2D="{Binding Int2DList}"/>

Note that you'll have to use ItemsSource2D instead of ItemsSource. I also fixed some styling issues which can be seen in the screenshot below. To use this style add the UseModifiedDataGridStyle="True", otherwise it'll be the default style. The source for the library and test application which I built this in can be downloaded from here.

alt text

DataGrid2D will only work for the .NET 4 DataGrid and not for WPF Toolkit, .NET 3.5. I'll make a version for that if anyone would want it.

OLD POST
Updated eariler version which only could handle square arrays (bug).
Here is an implementation that can bind a 2D array to the WPF datagrid.

Say we have this 2D array

private int[,] m_intArray = new int[5, 5];
...
for (int i = 0; i < 5; i++)
{
    for (int j = 0; j < 5; j++)
    {
        m_intArray[i,j] = (i * 10 + j);
    }
}

And then we want to bind this 2D array to the WPF DataGrid and the changes we make shall be reflected in the array. To do this I used Eric Lippert's Ref class from this thread.

public class Ref<T>  
{ 
    private readonly Func<T> getter;  
    private readonly Action<T> setter; 
    public Ref(Func<T> getter, Action<T> setter)  
    {  
        this.getter = getter;  
        this.setter = setter;  
    } 
    public T Value { get { return getter(); } set { setter(value); } }  
} 

Then I made a static helper class with a method that could take a 2D array and return a DataView using the Ref class above.

public static DataView GetBindable2DArray<T>(T[,] array)
{
    DataTable dataTable = new DataTable();
    for (int i = 0; i < array.GetLength(1); i++)
    {
        dataTable.Columns.Add(i.ToString(), typeof(Ref<T>));
    }
    for (int i = 0; i < array.GetLength(0); i++)
    {
        DataRow dataRow = dataTable.NewRow();
        dataTable.Rows.Add(dataRow);
    }
    DataView dataView = new DataView(dataTable);
    for (int i = 0; i < array.GetLength(0); i++)
    {
        for (int j = 0; j < array.GetLength(1); j++)
        {
            int a = i;
            int b = j;
            Ref<T> refT = new Ref<T>(() => array[a, b], z => { array[a, b] = z; });
            dataView[i][j] = refT;
        }
    }
    return dataView;
}

This would almost be enough to bind to but the Path in the Binding will point to the Ref object instead of the Ref.Value which we need so we have to change this when the Columns get generated.

<DataGrid Name="c_dataGrid"
          RowHeaderWidth="0"
          ColumnHeaderHeight="0"
          AutoGenerateColumns="True"
          AutoGeneratingColumn="c_dataGrid_AutoGeneratingColumn"/>

private void c_dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    DataGridTextColumn column = e.Column as DataGridTextColumn;
    Binding binding = column.Binding as Binding;
    binding.Path = new PropertyPath(binding.Path.Path + ".Value");
}

And after this we can use

c_dataGrid.ItemsSource = BindingHelper.GetBindable2DArray<int>(m_intArray);

And the output will look like this

alt text

Any changes made in the DataGrid will be reflected in the m_intArray.

Meleak