我如何知道 ListBoxItem 是否是 Wpf 的 ListBox 中的最后一项? [英] How can i know if a ListBoxItem is the last item inside a Wpf's ListBox?

查看:20
本文介绍了我如何知道 ListBoxItem 是否是 Wpf 的 ListBox 中的最后一项?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我如何知道 ListBoxItem 是否是集合的最后一项(在 ItemContainerStyleItemContainer 的模板中)在 Wpf 的 ListBox 内?

How can i know if a ListBoxItem is the last item of the collection (in the ItemContainerStyle or in the ItemContainer's template) inside a Wpf's ListBox?

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

That question is because I need to know if an item is the last item to show it in other way. For example: suppose i want to show items separated by semi-colons but the last one: a;b;c

这在 html 和 ccs 中很容易做到,使用 ccs 选择器.但是,我如何在 Wpf 中做到这一点?

This is easy to do in html and ccs, using ccs selector. But, how can i do this in Wpf?

推荐答案

好像是 相当难以实现 ListBoxItem 的索引"附加属性来完成这项工作,我相信更简单的方法是在 MVVM 中实现.您可以将必要的逻辑(IsLast"属性等)添加到列表的实体类型,并让 ViewModel 处理它,并在修改或替换集合时更新它.

As it seems to be rather difficult to implement an "Index" attached property to ListBoxItem to do the job right, I believe the easier way to accomplish that would be in MVVM. You can add the logic necessary (a "IsLast" property, etc) to the entity type of the list and let the ViewModel deal with this, updating it when the collection is modified or replaced.

编辑

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

After some attempts, I managed to implement indexing of ListBoxItems (and consequently checking for last) using a mix of attached properties and inheriting ListBox. Check it out:

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

To test it, we setup a simple ViewModel and an Item class:

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>

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

In the view's code behind I put some logic to test the behavior of the solution when updating the collection:

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 以及添加、删除、插入项目.希望对您有用.

This solution can handle static lists and also ObservableCollections and adding, removing, inserting items to it. Hope you find it useful.

编辑

使用 CollectionViews 对其进行了测试,效果很好.

Tested it with CollectionViews and it works just fine.

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

In the first test, I changed Sort/GroupDescriptions in the ListBox.Items. When one of them was changed, the ListBox recreates the containeirs, and then PrepareContainerForItemOverride hits. As it looks for the right index in the ListBox.Items itself, the order is updated correctly.

在第二个中,我将 ViewModel 中的 Items 属性设置为 ListCollectionView.在这种情况下,当描述更改时,会引发 CollectionChanged 并且 ListBox 会按预期做出反应.

In the second I made the Items property in the ViewModel a ListCollectionView. In this case, when the descriptions were changed, the CollectionChanged was raised and the ListBox reacted as expected.

这篇关于我如何知道 ListBoxItem 是否是 Wpf 的 ListBox 中的最后一项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆