Using a Grid as the ItemsPanel for an ItemsControl in Silverlight 3

skb picture skb · Mar 6, 2010 · Viewed 7.9k times · Source

Is it possible to do something like this:

    <ListBox>
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBox Text="{Binding Text}" Grid.Column="{Binding Column}" Grid.Row="{Binding Row}"  />
            </DataTemplate>                
        </ListBox.ItemTemplate>
    </ListBox>

The items source would be something like a List of objects that had the Text, Column and Row properties.

Is this possible? I really want to have my data grid be data bound.

Answer

itowlson picture itowlson · Mar 6, 2010

What you have won't work because Silverlight wraps each item -- each instance of the DataTemplate -- in a ListBoxItem, and the Grid.Column and Grid.Row attached properties need to be applied to that ListBoxItem, not to the TextBox that becomes the content of that ListBoxItem.

The good news is that you can set attributes on the implicit ListBoxItem using ListBox.ItemContainerStyle.

The bad news is that ItemContainerStyle doesn't readily support binding. So you can't use it to set the Grid.Column and Grid.Row attached properties to attributes of the data item at hand.

One solution that I've used is to subclass ListBox and set up the binding in PrepareContainerForItemOverride. Here's a very crude, hardwired example:

public class GriderrificBox : ListBox
{
  protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
  {
    base.PrepareContainerForItemOverride(element, item);

    FrameworkElement fe = element as FrameworkElement;
    if (fe != null)
    {
      BindingOperations.SetBinding(fe, Grid.RowProperty,
        new Binding { Source = item, Path = new PropertyPath("Row") });
      BindingOperations.SetBinding(fe, Grid.ColumnProperty,
        new Binding { Source = item, Path = new PropertyPath("Column") });
    }
  }
}

Usage:

<local:GriderrificBox>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <TextBox Text="{Binding Text}" />
    </DataTemplate>
  </ListBox.ItemTemplate>
  <ListBox.ItemsPanel>
    <ItemsPanelTemplate>
      <Grid />
    </ItemsPanelTemplate>
  </ListBox.ItemsPanel>
</local:GriderrificBox>

There are (at least) two major uglinesses with this code: first, you still need to explicitly specify ItemsPanel in XAML, even though the control works only with Grid panels; and second, the binding paths are hardwired into the code. The first can be addressed by using the normal control default style mechanism, and the second by defining properties such as RowBindingPath and ColumnBindingPath which PrepareItemForContainerOverride can consult instead of using hardwired paths. Hopefully enough to get you going anyway!