如何在ItemsControl中的项之间添加分隔符

66

我需要在一个项控件中显示来自集合的数字列表。这些项目是: "1", "2", "3"

当它们被渲染时,我需要它们用逗号(或类似物)隔开。因此上述三个项目应该像这样:"1, 2, 3"

如何在不在列表末尾添加一个分隔符的情况下,给各个项目添加一个分隔符?

我不一定要使用项控件,但那是我开始使用的。

5个回答

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

我之所以看到你的问题,是因为我在寻找Silverlight中没有相对数据源的解决方案。


1
@foson:我从未找到过这样的方法。最终我使用了负边距来“切断”文本中末尾的“,”。感觉很不好,但是它起作用了。 - Kent Boogaart
使用该项的索引与零进行比较怎么样? - MikeKulls
很好,简单且工作良好。通过这个,我可以检测一个项目是否是集合中的第一个(在数据模板内)。但有没有办法检测它是否是最后一个项目?也许有“NextItem”或其他东西吗?请参见此问题:http://stackoverflow.com/questions/13613053/how-can-i-know-if-a-listboxitem-is-the-last-item-inside-a-wpfs-listbox. - Raúl Otaño
@GONeale - 这一定是与声明性语言有关,我知道XSLT和HTML在某种程度上让我感觉完全相同。 - jpierson
这是个不错的技巧。谢谢。我能够利用这个原则在我的项目之间添加空格,但不会在顶部或底部的项目上下方添加空格,方法是将边距设置为0,20,0,0,并使触发器将边距设置为0。 - stritch000
Chris在下面的回答中使用“AlternationCount”是一种更可靠的解决方案,特别是当列表计数发生变化时。 - Carter Medlin

34

当前被认可的答案为每个模板提供了一个XAML绑定错误,这让我担心会影响性能。相反,我使用AlternationIndex来隐藏第一个分隔符,如下所示。(受该答案的启发。)

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

2
有趣。我在使用被接受的答案后,遇到了逗号在列表计数更改后仍然存在的问题,然后回到这里。您的解决方案解决了我的问题,谢谢! - Carter Medlin
对我来说,在这一行中不带点也可以运行:<TextBlock Text="{Binding }"/> 而不是 <TextBlock Text="{Binding .}"/> - luka
1
AlternationIndex解决方案也适用于<ControlTemplate />,而其他解决方案则不然,这意味着它可以在没有数据绑定的情况下使用(其中您的项目在XAML中指定)。 简直太棒了。 - bokibeg

5

如果需要更加通用的Silverlight兼容解决方案,可以从ItemsControl(SeperatedItemsControl)派生出一个控件。每个项目都包装在SeperatedItemsControlItem中,就像ListBox的ListBoxItem一样。SeperatedItemsControlItem的模板包含一个分隔符和一个ContentPresenter。集合中第一个元素的分隔符是隐藏的。您可以轻松修改此解决方案以创建项目之间的水平条形分隔符,这也是我创建它的原因。

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# 代码:

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

4

您还可以使用多种数据绑定方式来绑定ItemsControl.AlternationIndex和ItemsControl.Count,并将AlternationIndex与Count进行比较,以确定您是否为最后一项。

将AlternationIndex设置为足够大的值,以适应所有项目,然后创建一个LastItemConverter并编写一个类似于以下内容的Convert方法:

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var alterationCount = (int)values[0];
        var itemCount = (int)values[1];
        if (itemCount > 1)
        {
            return alterationCount == (itemCount - 1) ? Visibility.Collapsed : Visibility.Visible;
        }

        return Visibility.Collapsed;
    }

1
可以在模板项的内容之前放置分隔符。然后只需绑定到AlternationIndex并在第一个项目中隐藏分隔符(AlternationIndex == 0)即可。 - baSSiLL

1

我想我应该提供我最终使用的解决方案。

我最终将我的项目集合绑定到 TextBlock 的文本上,并使用值转换器将绑定的项目集合转换为格式化字符串。


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