使用WrapPanel作为ItemsPanel的ItemsControl - 组合“静态”子项和ItemsSource

时间:2015-10-28 08:52:59

标签: c# wpf xaml

使用ItemsControl并将WrapPanel设置为ItemsPanel,我试图实现此图像中所示的内容: ItemsControl with WrapPanel as ItemsPanel

XAML看起来像这样(经过修改使其更简单):

<ItemsControl ItemsSource="{Binding Animals}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel IsItemsHost="True" Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border Margin="5">
                <Image Source="{Binding ImageUrl}" />
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

基础ViewModel如下所示:

public class Zoo
{
    public ObservableCollection<Animal> Animals { get; set; } = new ObservableCollection<Animal>(); 

    public ICommand AddAnimal() => new DelegateCommand(() => Animals.Add(new Animal()));
}

public class Animal
{
    public string ImageUrl { get; set; }
}

ItemsControl的DataContext设置为Zoo的一个实例,并填充了4个动物。

问题: 如何添加一个看起来像其他元素的“静态”子元素,是WrapPanel的子元素,并与其他子元素包装?具体来说,我希望最后一个元素是一个添加按钮(上图中显示的绿色加号),它绑定到动物园的AddAnimal Command属性。

要求:

  • add-button-element必须与WrapPanel的其他子项一起包装。
  • add-button-element必须始终是最后一个(或第一个)元素,如果动物园中没有动物,也应该可见。
  • 将一个虚拟Animal添加到Animals集合中,然后使用样式修改最后一个子节点不是一个选项。底层模型在应用程序中使用了很多地方,因此在动物集合中放置一个假动物太过于苛刻。

我想过:

  • 使用将Animals集合复制到新集合的转换器,向副本添加一个虚拟Animal,并将其返回到ItemsControl的ItemsSource绑定。然后我可以将最后一个元素设置为添加按钮。但是,这不是一个选项,因为一些拖放逻辑需要能够通过ItemsControl的ItemsSource属性处理原始的ObservableCollection。
  • 对WrapPanel进行子类化并将add-button-element添加为子元素而不修改ItemsSource属性似乎是最佳选择(如果可能),但我无法弄清楚如何做到这一点。

其他信息:我使用:WPF,PRISM,C#6.0,.NET 4.0 Client Profile和MVVM模式。

对这个问题的任何想法?

解决方案:

@ kyriacos_k的答案为我解决了两个小修改:

  1. 如果DataTemplate是通过ItemsControl.ItemTemplate属性设置的,那么它不起作用,即Add-button会拾取DataTemplate并显示错误。我想这是设计的,因为MSDN声明:“ItemsControl使用CompositeCollection中的数据根据​​其ItemTemplate生成其内容”,来源:https://msdn.microsoft.com/en-us/library/system.windows.data.compositecollection%28v=vs.110%29.aspx
  2. 我不得不使用“绑定代理”来使AddAnimal Command绑定工作。 “直接”绑定语法或相关源都不起作用。我想这是因为添加按钮不是同一个可视树的一部分,并且它不会从ItemsControl中拾取DataContext。
  3. 最后这对我有用:

    <ItemsControl>
        <ItemsControl.Resources>
            <CollectionViewSource x:Key="AnimalCollection" Source="{Binding Animals}"/>
            <behaviors:BindingProxy x:Key="Proxy" DataContext="{Binding}"/>
            <DataTemplate DataType="{x:Type local:Animal}">
                <Border Margin="5">
                    <Image Source="{Binding ImageUrl}" />
                </Border>
            </DataTemplate>
        </ItemsControl.Resources>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel IsItemsHost="True" Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemsSource>
            <CompositeCollection>
                <CollectionContainer Collection="{Binding Source={StaticResource AnimalCollection}}"/>
                <Border Margin="5">
                    <Button Command="{Binding DataContext.AddAnimal, Source={StaticResource Proxy}}">
                        <Image Source="SourceToPlusSign"/>
                    </Button>
                </Border>
            </CompositeCollection>
        </ItemsControl.ItemsSource>
    </ItemsControl>
    

    BindingProxy的代码在这里(直接从Binding Visibility for DataGridColumn in WPF抓取):

    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }
    
        public object DataContext
        {
            get { return GetValue(DataContextProperty); }
            set { SetValue(DataContextProperty, value); }
        }
    
        public static readonly DependencyProperty DataContextProperty =
            DependencyProperty.Register("DataContext", typeof(object),
                                         typeof(BindingProxy));
    }
    

2 个答案:

答案 0 :(得分:8)

您可以使用CompositeCollection

以非常简洁的方式完成此操作
<ItemsControl>
    <ItemsControl.Resources>
        <CollectionViewSource x:Key="AnimalCollection" Source="{Binding Animals}"/>
    </ItemsControl.Resources>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel IsItemsHost="True" Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border Margin="5">
                <Image Source="{Binding ImageUrl}" />
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsSource>
        <CompositeCollection>
            <CollectionContainer Collection="{Binding Source={StaticResource AnimalCollection}}"/>
            <Border Margin="5">
                <Button Command="{Binding AddAnimal}">
                    <Image Source="YourAddButtonSource"/>
                </Button>
            </Border>
        </CompositeCollection>
    </ItemsControl.ItemsSource>
</ItemsControl>

当然,如果您希望首先显示添加按钮,只需将Border(包含Button)的顺序与CollectionContainer中的CompositeCollection进行交换标签

答案 1 :(得分:0)

看看CompositeCollection。它允许您向ItemsSource绑定添加其他项目:

<Window.Resources>
   <CollectionViewSource x:Key="AnimalViewSource" Source="{Binding Animals}"/>
</Window.Resources>

<ItemsControl>
    <ItemsControl.ItemsSource>
        <CompositeCollection>
             <local:Animal ImageUrl="somepath/plussign.png" />
             <CollectionContainer Collection="{Binding Source={StaticResource AnimalViewSource}}"/>
         </CompositeCollection>
    </ItemsControl.ItemsSource>

    ... ItemsPanel, ItemsTemplate, etc. follow here ...
</ItemsControl>