c# - ItemsControl with WrapPanel 作为 ItemsPanel - 组合一个 "static"子项和 ItemsSource

标签 c# wpf xaml

使用带有 WrapPanel 设置为 ItemsPanel 的 ItemsControl,我试图实现此图像中所示的内容: 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 的子元素,并与其他子元素一起包装?具体来说,我希望最后一个元素是一个添加按钮(上图中显示的绿色加号),它绑定(bind)到 Zoo 的 AddAnimal Command 属性。

要求:

  • add-button-element 必须与 WrapPanel 的其他子元素一起包装。
  • add-button-element 必须始终是最后一个(或第一个)元素,并且如果动物园中没有动物,它也应该是可见的。
  • 向 Animals 集合中添加一个虚拟 Animal,然后使用样式修改最后一个子项不是一种选择。底层模型在应用程序中的很多地方使用,因此在 Animals 集合中有一个虚拟的 Animal 漂浮着太老套了。

我想过:

  • 使用将 Animals 集合复制到新集合的转换器,将虚拟 Animal 添加到副本,并将其返回到 ItemsControl 的 ItemsSource 绑定(bind)。然后我可以将最后一个元素设置为添加按钮。但是,这不是一个选项,因为某些拖放逻辑需要能够通过 ItemsControl 的 ItemsSource 属性在原始 ObservableCollection 上工作。
  • 子类化 WrapPanel 并将添加按钮元素添加为子元素而不修改 ItemsSource 属性似乎是最佳选择(如果可能),但我不知道该怎么做。

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

对这个问题有什么想法吗?

解决方案:

@kyriacos_k 的回答通过两个小修改为我解决了这个问题:

  1. 如果 DataTemplate 是通过 ItemsControl.ItemTemplate 属性设置的,则它不起作用,即添加按钮会选择 DataTemplate 并显示不正确。我想这是设计使然,正如 MSDN 所述:“ItemsControl 使用 CompositeCollection 中的数据根据​​其 ItemTemplate 生成其内容”,来源:https://msdn.microsoft.com/en-us/library/system.windows.data.compositecollection%28v=vs.110%29.aspx )
  2. 我必须使用“绑定(bind)代理”才能使 AddAnimal 命令绑定(bind)生效。 “直接”绑定(bind)语法或相对来源均无效。我猜这是因为添加按钮不是同一可视化树的一部分,并且不知何故它不会从 ItemsControl 中获取 DataContext。

最后这对我有用:

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

最佳答案

您可以使用 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 )与 CollectionContainerCompositeCollection标签。

关于c# - ItemsControl with WrapPanel 作为 ItemsPanel - 组合一个 "static"子项和 ItemsSource,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33386566/

相关文章:

c# - 从 C++ ASIO 库用 C# 录制音频流

wpf - 我可以有两个 ContentPresenter 指向 ContentTemplate 中的单个 Content 或 ContentSource 吗?

c# - 如何从 ViewModel 中访问 RichEditBox 控件?

c# - 将 SQLite 数据填充到 UWP 应用程序中

c# - 改变 View 模型

c# - 使用 VS2010 运行 StyleCop 时出现 CA2000 Microsoft.Reliability 错误

c# - 为什么 LINQ 查询会在我尝试获取某种类型的计数时抛出异常

c# - 拖动后获取 Canvas 子元素的位置。

wpf - 是否有在 XAML 中应用 Binding.ValidationRules 的简洁方法

c# - 完全重启后如何卸载未使用的 COM 对象/库?