使用将WrapPanel设置为ItemsPanel的ItemsControl,我试图实现如下所示的效果:
XAML代码如下(已进行简化):
ItemsControl 的 DataContext 被设置为一个 Zoo 实例,并使用 4 个 Animal 填充。
问题: 如何添加一个“静态”子元素,看起来像其他元素,是 WrapPanel 的子元素,并与其他子项一起换行?具体而言,我希望最后一个元素是一个添加按钮(上图中显示的绿色加号),它绑定到 Zoo 的 AddAnimal Command 属性。
要求: 添加按钮元素必须与 WrapPanel 的其他子项一起换行。 添加按钮元素必须始终是最后一个(或第一个)元素,并且如果 Zoo 中没有 Animals,则应该可见。 向 Animals 集合添加虚拟 Animal,然后使用样式修改最后一个子元素不是选项。底层模型在应用程序的许多地方都被使用,因此在 Animals 集合中漂浮着虚拟 Animal 过于投机,不可取。
我考虑过: 使用转换器将 Animals 集合复制到新集合中,在副本中添加虚拟 Animal,然后将其返回到 ItemsControl 的 ItemsSource 绑定。然后,我可以将最后一个元素样式化为添加按钮。但是,这不是选项,因为某些拖放逻辑需要能够通过 ItemsControl 的 ItemsSource 属性在原始 ObservableCollection 上工作。 子类化 WrapPanel 并将添加按钮元素添加为子元素而不修改 ItemsSource 属性似乎是最佳选择(如果可能),但我无法弄清楚如何做到这一点。
其他信息:我使用的是 WPF、PRISM、C# 6.0、.NET 4.0 客户端框架和 MVVM 模式。
有任何关于这个问题的想法吗?
解决方案: @kyriacos_k 的答案对我有所帮助,但需要进行两个小修改:
1. 如果通过 ItemsControl.ItemTemplate 属性设置 DataTemplate,则不起作用,即 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 命令绑定正常工作。直接绑定语法或相对源都不起作用。我猜这是因为添加按钮不是同一个可视树的一部分,某种程度上它没有从 ItemsControl 获取 DataContext。
最终,这就是对我有用的东西:
![ItemsControl with WrapPanel as ItemsPanel](https://istack.dev59.com/MmXzf.webp)
<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 个 Animal 填充。
问题: 如何添加一个“静态”子元素,看起来像其他元素,是 WrapPanel 的子元素,并与其他子项一起换行?具体而言,我希望最后一个元素是一个添加按钮(上图中显示的绿色加号),它绑定到 Zoo 的 AddAnimal Command 属性。
要求: 添加按钮元素必须与 WrapPanel 的其他子项一起换行。 添加按钮元素必须始终是最后一个(或第一个)元素,并且如果 Zoo 中没有 Animals,则应该可见。 向 Animals 集合添加虚拟 Animal,然后使用样式修改最后一个子元素不是选项。底层模型在应用程序的许多地方都被使用,因此在 Animals 集合中漂浮着虚拟 Animal 过于投机,不可取。
我考虑过: 使用转换器将 Animals 集合复制到新集合中,在副本中添加虚拟 Animal,然后将其返回到 ItemsControl 的 ItemsSource 绑定。然后,我可以将最后一个元素样式化为添加按钮。但是,这不是选项,因为某些拖放逻辑需要能够通过 ItemsControl 的 ItemsSource 属性在原始 ObservableCollection 上工作。 子类化 WrapPanel 并将添加按钮元素添加为子元素而不修改 ItemsSource 属性似乎是最佳选择(如果可能),但我无法弄清楚如何做到这一点。
其他信息:我使用的是 WPF、PRISM、C# 6.0、.NET 4.0 客户端框架和 MVVM 模式。
有任何关于这个问题的想法吗?
解决方案: @kyriacos_k 的答案对我有所帮助,但需要进行两个小修改:
1. 如果通过 ItemsControl.ItemTemplate 属性设置 DataTemplate,则不起作用,即 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 命令绑定正常工作。直接绑定语法或相对源都不起作用。我猜这是因为添加按钮不是同一个可视树的一部分,某种程度上它没有从 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的代码(直接从WPF中DataGridColumn的可见性绑定复制):
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));
}
System.Windows.Data Error: 26 : ItemTemplate 和 ItemTemplateSelector 对于已经是 ItemsControl 容器类型的项将被忽略;Type='Border'
,但在视觉上它似乎工作得很好。 - kimmoli