How can a separator be added between items in an ItemsControl
<ItemsControl ItemsSource="{Binding Numbers}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <!-- could use a WrapPanel if more appropriate for your scenario --> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="commaTextBlock" Text=", "/> <TextBlock Text="{Binding .}"/> </StackPanel> <DataTemplate.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}"> <Setter Property="Visibility" TargetName="commaTextBlock" Value="Collapsed"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ItemsControl.ItemTemplate></ItemsControl>
I arrived at your question because I was looking for a solution in Silverlight, which does not have a previous data relative source.
The current accepted answer gave me a xaml binding error for every template, which I was concerned could be affecting performance. Instead, I did the below, using the AlternationIndex to hide the first separator. (Inspired by this answer.)
<ItemsControl ItemsSource="{Binding Numbers}" AlternationCount="{Binding RelativeSource={RelativeSource Self}, Path=Items.Count}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="SeparatorTextBlock" Text=", "/> <TextBlock Text="{Binding .}"/> </StackPanel> <DataTemplate.Triggers> <Trigger Property="ItemsControl.AlternationIndex" Value="0"> <Setter Property="Visibility" TargetName="SeparatorTextBlock" Value="Collapsed" /> </Trigger> </DataTemplate.Triggers> </DataTemplate> </ItemsControl.ItemTemplate></ItemsControl>
For a more generalized Silverlight-compatible solution, I derived a control from ItemsControl (SeperatedItemsControl). Each item is wrapped in a SeperatedItemsControlItem, just like ListBox's ListBoxItem. The template for SeperatedItemsControlItem contains a seperator and a ContentPresenter. The seperator for the first element in the collection is hidden. You can easily modify this solution to make a horizontal bar seperator between items, which is what I created it for.
MainWindow.xaml:
<Window x:Class="ItemsControlWithSeperator.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ItemsControlWithSeperator"mc:Ignorable="d"d:DesignHeight="300" d:DesignWidth="400"><UserControl.Resources> <local:ViewModel x:Key="vm" /></UserControl.Resources><Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource vm}"> <local:SeperatedItemsControl ItemsSource="{Binding Data}"> <local:SeperatedItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </local:SeperatedItemsControl.ItemsPanel> <local:SeperatedItemsControl.ItemContainerStyle> <Style TargetType="local:SeperatedItemsControlItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:SeperatedItemsControlItem" > <StackPanel Orientation="Horizontal"> <TextBlock x:Name="seperator">,</TextBlock> <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}"/> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </local:SeperatedItemsControl.ItemContainerStyle> </local:SeperatedItemsControl></Grid>
C# Code:
using System;using System.Windows;using System.Windows.Controls;namespace ItemsControlWithSeperator{ public class ViewModel { public string[] Data { get { return new[] { "Amy", "Bob", "Charlie" }; } } } public class SeperatedItemsControl : ItemsControl { public Style ItemContainerStyle { get { return (Style)base.GetValue(SeperatedItemsControl.ItemContainerStyleProperty); } set { base.SetValue(SeperatedItemsControl.ItemContainerStyleProperty, value); } } public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(SeperatedItemsControl), null); protected override DependencyObject GetContainerForItemOverride() { return new SeperatedItemsControlItem(); } protected override bool IsItemItsOwnContainerOverride(object item) { return item is SeperatedItemsControlItem; } protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { //begin code copied from ListBox class if (object.ReferenceEquals(element, item)) { return; } ContentPresenter contentPresenter = element as ContentPresenter; ContentControl contentControl = null; if (contentPresenter == null) { contentControl = (element as ContentControl); if (contentControl == null) { return; } } DataTemplate contentTemplate = null; if (this.ItemTemplate != null && this.DisplayMemberPath != null) { throw new InvalidOperationException(); } if (!(item is UIElement)) { if (this.ItemTemplate != null) { contentTemplate = this.ItemTemplate; } } if (contentPresenter != null) { contentPresenter.Content = item; contentPresenter.ContentTemplate = contentTemplate; } else { contentControl.Content = item; contentControl.ContentTemplate = contentTemplate; } if (ItemContainerStyle != null && contentControl.Style == null) { contentControl.Style = ItemContainerStyle; } //end code copied from ListBox class if (this.Items.Count > 0) { if (object.ReferenceEquals(this.Items[0], item)) { var container = element as SeperatedItemsControlItem; container.IsFirstItem = true; } } } protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (Items.Count > 1) { var container = (ItemContainerGenerator.ContainerFromIndex(1) as SeperatedItemsControlItem); if (container != null) container.IsFirstItem = false; } if (Items.Count > 0) { var container = (ItemContainerGenerator.ContainerFromIndex(0) as SeperatedItemsControlItem); if (container != null) container.IsFirstItem = true; } } } public class SeperatedItemsControlItem : ContentControl { private bool isFirstItem; public bool IsFirstItem { get { return isFirstItem; } set { if (isFirstItem != value) { isFirstItem = value; var seperator = this.GetTemplateChild("seperator") as FrameworkElement; if (seperator != null) { seperator.Visibility = isFirstItem ? Visibility.Collapsed : Visibility.Visible; } } } } public override void OnApplyTemplate() { base.OnApplyTemplate(); if (IsFirstItem) { var seperator = this.GetTemplateChild("seperator") as FrameworkElement; if (seperator != null) { seperator.Visibility = Visibility.Collapsed; } } } }}