我怎么知道ListBoxItem是否是Wpf的ListBox中的最后一项?

时间:2012-11-28 19:38:14

标签: c# wpf listbox listboxitem

我怎么知道ListBoxItem是否是Wpf中的最后一项(在ItemContainerStyleItemContainer模板中)? s ListBox

这个问题是因为我需要知道某个项目是否是以其他方式显示它的最后一项。例如:假设我想显示以分号分隔的项目,但是最后一项: a; b; c

使用ccs选择器在html和ccs中很容易做到这一点。但是,我怎么能在Wpf中做到这一点?

1 个答案:

答案 0 :(得分:5)

由于似乎是rather difficult to implement ListBoxItem的“索引”附加属性来完成工作,我相信更容易实现的方法是在MVVM中。 您可以将必要的逻辑(“IsLast”属性等)添加到列表的实体类型中,让ViewModel处理此事务,在修改或替换集合时更新它。

编辑

经过一些尝试,我设法使用附加属性的混合并继承ListBox来实现ListBoxItems的索引(并因此检查最后一个)。看看:

public class IndexedListBox : System.Windows.Controls.ListBox
{
    public static int GetIndex(DependencyObject obj)
    {
        return (int)obj.GetValue(IndexProperty);
    }
    public static void SetIndex(DependencyObject obj, int value)
    {
        obj.SetValue(IndexProperty, value);
    }
    /// <summary>
    /// Keeps track of the index of a ListBoxItem
    /// </summary>
    public static readonly DependencyProperty IndexProperty =
        DependencyProperty.RegisterAttached("Index", typeof(int), typeof(IndexedListBox), new UIPropertyMetadata(0));


    public static bool GetIsLast(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsLastProperty);
    }
    public static void SetIsLast(DependencyObject obj, bool value)
    {
        obj.SetValue(IsLastProperty, value);
    }
    /// <summary>
    /// Informs if a ListBoxItem is the last in the collection.
    /// </summary>
    public static readonly DependencyProperty IsLastProperty =
        DependencyProperty.RegisterAttached("IsLast", typeof(bool), typeof(IndexedListBox), new UIPropertyMetadata(false));


    protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
    {
        // We capture the ItemsSourceChanged to check if the new one is modifiable, so we can react to its changes.

        var oldSource = oldValue as INotifyCollectionChanged;
        if(oldSource != null)
            oldSource.CollectionChanged -= ItemsSource_CollectionChanged;

        var newSource = newValue as INotifyCollectionChanged;
        if (newSource != null)
            newSource.CollectionChanged += ItemsSource_CollectionChanged;

        base.OnItemsSourceChanged(oldValue, newValue);
    }

    void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        this.ReindexItems();
    }

    protected override void PrepareContainerForItemOverride(System.Windows.DependencyObject element, object item)
    {
        // We set the index and other related properties when generating a ItemContainer
        var index = this.Items.IndexOf(item); 
        SetIsLast(element, index == this.Items.Count - 1);
        SetIndex(element, index);

        base.PrepareContainerForItemOverride(element, item);
    }

    private void ReindexItems()
    {
        // If the collection is modified, it may be necessary to reindex all ListBoxItems.
        foreach (var item in this.Items)
        {
            var itemContainer = this.ItemContainerGenerator.ContainerFromItem(item);
            if (itemContainer == null) continue;

            int index = this.Items.IndexOf(item);
            SetIsLast(itemContainer, index == this.Items.Count - 1);
            SetIndex(itemContainer, index);
        }
    }
}

为了测试它,我们设置了一个简单的ViewModel和一个Item类:

public class ViewModel : INotifyPropertyChanged
{
    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion

    private ObservableCollection<Item> items;
    public ObservableCollection<Item> Items
    {
        get { return this.items; }
        set
        {
            if (this.items != value)
            {
                this.items = value;
                this.OnPropertyChanged("Items");
            }
        }
    }

    public ViewModel()
    {
        this.InitItems(20);
    }

    public void InitItems(int count)
    {

        this.Items = new ObservableCollection<Item>();
        for (int i = 0; i < count; i++)
            this.Items.Add(new Item() { MyProperty = "Element" + i });
    }

}

public class Item
{
    public string MyProperty { get; set; }

    public override string ToString()
    {
        return this.MyProperty;
    }
}

观点:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication3"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication3.MainWindow"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <DataTemplate x:Key="DataTemplate">
        <Border x:Name="border">
            <StackPanel Orientation="Horizontal">
                <TextBlock TextWrapping="Wrap" Text="{Binding (local:IndexedListBox.Index), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Margin="0,0,8,0"/>
                <TextBlock TextWrapping="Wrap" Text="{Binding (local:IndexedListBox.IsLast), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Margin="0,0,8,0"/>
                <ContentPresenter Content="{Binding}"/>
            </StackPanel>
        </Border>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding (local:IndexedListBox.IsLast), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Value="True">
                <Setter Property="Background" TargetName="border" Value="Red"/>
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
</Window.Resources>
<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="0.949*"/>
    </Grid.RowDefinitions>

    <local:IndexedListBox ItemsSource="{Binding Items}" Grid.Row="1" ItemTemplate="{DynamicResource DataTemplate}"/>

    <Button Content="Button" HorizontalAlignment="Left" Width="75" d:LayoutOverrides="Height" Margin="8" Click="Button_Click"/>
    <Button Content="Button" HorizontalAlignment="Left" Width="75" Margin="110,8,0,8" Click="Button_Click_1"  d:LayoutOverrides="Height"/>
    <Button Content="Button" Margin="242,8,192,8" Click="Button_Click_2"  d:LayoutOverrides="Height"/>
</Grid>
</Window>

在视图后面的代码中,我提出了一些逻辑来测试更新集合时解决方案的行为:

public partial class MainWindow : Window
{
    public ViewModel ViewModel { get { return this.DataContext as ViewModel; } }

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.ViewModel.Items.Insert( 5, new Item() { MyProperty= "NewElement" });
    }

    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        this.ViewModel.Items.RemoveAt(5);
    }

    private void Button_Click_2(object sender, RoutedEventArgs e)
    {
        this.ViewModel.InitItems(new Random().Next(10,30));
    }
}

此解决方案可以处理静态列表以及ObservableCollections并添加,删除,插入项目。希望你觉得它很有用。

修改

使用CollectionViews测试它,它工作得很好。

在第一个测试中,我更改了ListBox.Items中的Sort / GroupDescriptions。当其中一个被更改时,ListBox重新创建容器,然后PrepareContainerForItemOverride命中。当它在ListBox.Items本身中查找正确的索引时,订单会正确更新。

在第二部分中,我将ViewModel中的Items属性设为ListCollectionView。在这种情况下,更改描述时,会引发CollectionChanged并且ListBox会按预期进行响应。