Использование сетки в качестве элемента ItemsPanel для элемента ItemsControl в Silverlight 3

Можно ли сделать что-то вроде этого:

    <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>

Источником элементов будет что-то вроде списка объектов, у которых были свойства Text, Column и Row.

Возможно ли это? Я действительно хочу, чтобы моя сеть данных была привязана к данным.

Ответ 1

То, что у вас есть, не будет работать, потому что Silverlight обертывает каждый элемент - каждый экземпляр DataTemplate - в ListBoxItem, а свойства Grid.Column и Grid.Row должны применяться к этому элементу ListBoxItem, а не к TextBox, который становится содержимым этого ListBoxItem.

Хорошей новостью является то, что вы можете устанавливать атрибуты на неявный ListBoxItem, используя ListBox.ItemContainerStyle.

Плохая новость в том, что ItemContainerStyle не поддерживает привязку. Поэтому вы не можете использовать его для установки атрибутов Grid.Column и Grid.Row для атрибутов элемента данных.

Одним из решений, которое я использовал, является подкласс ListBox и настройка привязки в PrepareContainerForItemOverride. Здесь очень грубый, прочный пример:

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") });
    }
  }
}

Использование:

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

В этом коде есть (по крайней мере) два основных уродства: во-первых, вам все равно нужно явно указать ItemsPanel в XAML, хотя управление работает только с панелями Grid; и, во-вторых, пути привязки жестко привязаны к коду. Первый может быть рассмотрен с использованием стандартного механизма стандартного управления по умолчанию, а второй - путем определения таких свойств, как RowBindingPath и ColumnBindingPath, которые PrepareItemForContainerOverride может проконсультироваться вместо использования проводных путей. Надеюсь, вы все равно пойдете!

Ответ 2

Grid просто не подходит для использования, которое вы пытаетесь установить здесь. Ожидается, что набор доступных строк и столбцов будет определен заранее, прежде чем вы начнете назначать элементы ячейкам.

Если вы пытаетесь создать окно списка, которое использует как горизонтальное, так и вертикальное пространство, то, возможно, WrapPanel из Silverlight Toolkit будет лучше.

С другой стороны, если вы пытаетесь создать "сетку данных", рассмотрите возможность переноса или группировки столбцов в каждой строке модели, тогда вы можете использовать DataGrid вместо ListBox

Ответ 3

Я нашел другое интересное решение для этой проблемы: http://www.scottlogic.co.uk/blog/colin/2010/11/using-a-grid-as-the-panel-for-an-itemscontrol/

Пример выполняется с помощью ItemsCountrol - но я уверен, что он также работает с ListBox

Результат выглядит следующим образом:

<ItemsControl ItemsSource="{Binding}">  
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <!-- use the ItemsPerRow attached property to dynamically add rows -->
      <Grid local:GridUtils.ItemsPerRow="1"
          ShowGridLines="True"/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

и необходимо реализовать свойство local:GridUtils.ItemsPerRow.

Ответ 4

Это будет работать, только если вы знаете, сколько строк и столбцов вам нужно, и только в Silverlight 5. (Вы не можете связать значение в свойстве setter в silverlight 4.)

<Grid x:Name="LayoutRoot" Background="White">
        <ItemsControl x:Name="ic" Background="#FFE25454">
            <ItemsControl.Resources>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Grid.Row" Value="{Binding X}"/>
                    <Setter Property="Grid.Column" Value="{Binding Y}"/>
                </Style>
            </ItemsControl.Resources>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid ShowGridLines="True">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions></Grid>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>

                    <TextBlock Text="{Binding text}"/>

                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

Ответ 5

Если вы заинтересованы в поддержке такого сценария в будущей версии Silverlight, проголосуйте за портирование макета Adobe Flex Grid, который будет работать отлично в таком сценарии

Ответ 6

Вам просто нужно создать два прикрепленных свойства для Grid (например, ColumnsNumber и RowsNumber, которые будут заполнять коллекции ColumnDefinitions и RowDefenitions). А затем переопределите элемент ItemContainerStyle по умолчанию в ItemsControl (потому что все элементы в ItemsControl завернуты ContentPresenters). Пример кода:

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid
                behavior:GridBehavior.ColumnsNumber="{Binding}"
                behavior:GridBehavior.RowsNumber="{Binding}">
            </Grid>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemsSource>
        <Binding />
    </ItemsControl.ItemsSource>

    <ItemsControl.ItemTemplate>
        <DataTemplate />
    </ItemsControl.ItemTemplate>

    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Grid.Column" Value="{Binding}" />
            <Setter Property="Grid.Row" Value="{Binding}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>