在Silverlight 3中,将网格用作ItemsControl的ItemsPanel

11

有没有可能做到这样的事情:

    <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 属性的对象列表。

这个可行吗?我真的想让我的数据网格绑定数据。


你的意图不是很清楚。你真的想要 ListBox 的语义吗?即一个包含 N 个项目(可能需要滚动)的列表,其中一个或多个项目可以被选择? - AnthonyWJones
6个回答

3
你所拥有的不会起作用,因为Silverlight将每个项目(DataTemplate的每个实例)都包装在一个ListBoxItem中,而Grid.Column和Grid.Row附加属性需要应用于该ListBoxItem,而不是成为该ListBoxItem内容的TextBox。
好消息是,你可以使用ListBox.ItemContainerStyle在隐式ListBoxItem上设置属性。
坏消息是,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>

这段代码存在(至少)两个主要问题:首先,即使控件仅适用于网格面板,您仍需要在XAML中显式指定ItemsPanel;其次,绑定路径被硬编码到代码中。第一个问题可以通过使用正常的控件默认样式机制来解决,第二个问题可以通过定义属性,例如RowBindingPath和ColumnBindingPath,供PrepareItemForContainerOverride查询而不是使用硬编码路径来解决。希望这足以帮助您入门!


仍然存在一个问题,即Grid需要在其RowDefinitionsColumnDefinitions集合中定义行和列的集合。 - AnthonyWJones
我不知道这是否有效。问题的一部分是在SL3中没有BindingOperations(至少我找不到)。我尝试在元素对象上执行SetBinding,但也不起作用。itowlson:您是否已经在SL3中使其工作? - skb
BindingOperations是System.Windows.Data命名空间中的静态类。是的,我已经在SL3中使用过它(尽管使用的是Canvas而不是Grid,但概念是相同的,并对AnthonyWJones提出的问题表示应有的认可)。 - itowlson
抱歉打扰了,但我发现这个非常有用,并且有一个建议使其更加灵活。我使用了您的方法,但是在自定义ItemsControl中为每个相关的“路径”添加了额外的依赖属性,例如RowPath。这样,在PrepareContainerForItemOverride中就没有硬编码字符串了,我只需为DP设置默认值,让消费者的XAML在需要时设置一个新值。 - Paul

2
我发现了另一个有趣的解决方案: http://www.scottlogic.co.uk/blog/colin/2010/11/using-a-grid-as-the-panel-for-an-itemscontrol/ 这个示例是使用ItemsControl完成的,但我相信它也适用于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的附加属性。


2

只有在Silverlight 5中,且您知道需要多少行和列时,此方法才能生效。(在Silverlight 4中无法在setter属性中绑定值。)

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

2
Grid并不适合您在此处尝试的用法。它期望在分配单元格元素之前先定义可用行和列的集合。
如果您想创建一个利用水平和垂直空间的列表框,那么Silverlight Toolkit中的WrapPanel可能是更好的基础。
另一方面,如果您想创建一个“数据网格”,则考虑在模型中转置或分组每行中的列,然后可以使用DataGrid而不是ListBox

0

如果您有兴趣在未来版本的Silverlight中支持这种场景,请投票支持Adobe Flex Grid layout移植,它将在这种情况下完美地工作。


-1

你只需要为 Grid 创建两个附加属性(比如 ColumnsNumber 和 RowsNumber),它们会填充 ColumnDefinitions 和 RowDefenitions 集合。然后,在 ItemsControl 中覆盖默认的 ItemContainerStyle(因为所有 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>

1
Silverlight 中没有 ItemContainerStyle。 - Paul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接