web-dev-qa-db-fra.com

Comment un séparateur peut-il être ajouté entre des éléments dans un ItemsControl

J'ai besoin d'afficher une liste de numéros d'une collection dans un contrôle d'objets. Les articles sont donc: "1", "2", "3".

Quand ils sont rendus, j'ai besoin d'eux séparés par une virgule (ou quelque chose de similaire). Ainsi, les 3 éléments ci-dessus ressembleraient à ceci: "1, 2, 3".

Comment puis-je ajouter un séparateur aux éléments individuels, sans en avoir un cloué à la fin de la liste?

Je ne suis pas bloqué sur l'utilisation d'un ItemsControl, mais c'est ce que j'avais commencé à utiliser.

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

Je suis arrivé à votre question parce que je cherchais une solution dans Silverlight, qui n'a pas de source relative de données précédente.

105
Kent Boogaart

La réponse actuellement acceptée m'a donné une erreur de liaison xaml pour chaque modèle, ce qui, selon moi, pouvait affecter les performances. Au lieu de cela, j'ai fait ce qui suit, en utilisant AlternationIndex pour masquer le premier séparateur. (Inspiré par cette réponse .)

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

Pour une solution compatible Silverlight plus généralisée, j'ai dérivé un contrôle de ItemsControl (SeperatedItemsControl). Chaque élément est enveloppé dans un SeperatedItemsControlItem, tout comme ListBoxItem de ListBox. Le modèle de SeperatedItemsControlItem contient un séparateur et un ContentPresenter. Le séparateur du premier élément de la collection est masqué. Vous pouvez facilement modifier cette solution pour créer un séparateur à barres horizontales entre les éléments, ce pour quoi je l'ai créé.

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>

Code 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;
                }
            }
        }
    }
}
5
foson

Vous pouvez également effectuer plusieurs liaisons vers ItemsControl.AlternationIndex et ItemsControl.Count et comparer AlternationIndex à Count pour voir si vous êtes le dernier élément.

Réglez AlternationIndex suffisamment haut pour accueillir tous vos articles, puis créez un LastItemConverter avec une méthode Convert ressemblant à ceci:

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

J'ai pensé que je devrais donner la solution avec laquelle je me suis retrouvé.

J'ai fini par lier ma collection d'éléments au texte d'un TextBlock et à utiliser un convertisseur de valeur pour changer la collection liée d'éléments dans la chaîne formatée.

1
Nathan