How can i know if a ListBoxItem is the last item inside a Wpf's ListBox?

眉间皱痕 提交于 2019-12-02 11:42:12

问题


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?

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

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


回答1:


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.

EDIT

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

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

The view:

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

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

EDIT

Tested it with CollectionViews and it works just fine.

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.

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.



来源:https://stackoverflow.com/questions/13613053/how-can-i-know-if-a-listboxitem-is-the-last-item-inside-a-wpfs-listbox

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!