如何绑定到列表中的最后一项?

How can I bind to the last item in a list?

在 WPF 中使用 XAML 时,我们可以轻松地绑定到列表中的第一项,如下所示:

<TextBlock Text="{Binding Model.Persons[0].FullName}"/>

但是如果我想绑定到最后一项怎么办?假设我不知道列表中可能有多少项,否则我可以很容易地使用该索引。

<TextBlock Text="{Binding Model.Persons.Last.FullName}"/>

不幸的是,以上都不起作用。有谁知道如何绑定到最后一项吗?

您可以在视图模型 LastItem 中创建一个新的 属性 并绑定到此 属性。

private Person lastItem;
public Person LastItem
{
    get { return lastItem; }
    set
    {
        lastItem= value;
        OnPropertyChanged();
    }
}

在您加载的代码后添加一个新行 Persons:

...
LastItem = Persons.LastOrDefault();
...

Xaml:

<TextBlock Text="{Binding Model.LastItem.FullName}"/>

这是代理代码。 我写得很快,没有检查-现在没有足够的空闲时间。 不过代码很简单,应该不会有错误。 如果事情没有解决,那就写吧。 我会尽力修复它。

using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;

namespace Proxy
{
    public class ProxyLastItem : Freezable
    {
        protected override Freezable CreateInstanceCore()
            => new ProxyLastItem();


        /// <summary>Observable Collection: <see cref="INotifyCollectionChanged"/> or <see cref="IBindingList"/>.</summary>
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        /// <summary><see cref="DependencyProperty"/> for property <see cref="ItemsSource"/>.</summary>
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(ProxyLastItem), new PropertyMetadata(null, ItemsSourceChanged));

        private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ProxyLastItem proxyLast = (ProxyLastItem)d;

            IEnumerable collection = e.OldValue as IEnumerable;

            if (collection != null)
            {
                if (collection is INotifyCollectionChanged observableCollection)
                {
                    observableCollection.CollectionChanged -= proxyLast.OnCollectionChanged;
                    proxyLast.IsObservableCollection = false;
                }
                if (collection is IBindingList bindingList)
                {
                    bindingList.ListChanged -= proxyLast.OnListChanged;
                    proxyLast.IsBindingList = false;
                }

            }


            collection = e.NewValue as IEnumerable;
            if (collection == null)
            {
                proxyLast.LastItem = null;
            }
            else
            {
                proxyLast.SetLastItem(collection);
                if (collection is INotifyCollectionChanged observableCollection)
                {
                    observableCollection.CollectionChanged += proxyLast.OnCollectionChanged;
                    proxyLast.IsObservableCollection = true;
                }
                else if (collection is IBindingList bindingList)
                {
                    bindingList.ListChanged += proxyLast.OnListChanged;
                    proxyLast.IsBindingList = true;
                }
            }
        }

        private void SetLastItem(IEnumerable collection)
        {
            if (!ReferenceEquals(collection, ItemsSource))
                throw new Exception("The observable collection is not the same as the ItemsSource collection.");

            if (collection is IList list)
            {
                if (list.Count == 0)
                    LastItem = null;
                else
                    LastItem = list[list.Count - 1];
            }
            else
                LastItem = collection.Cast<object>().LastOrDefault();
        }

        private void OnListChanged(object sender, ListChangedEventArgs e)
            => SetLastItem((IEnumerable)sender);

        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            => SetLastItem((IEnumerable)sender);

        /// <summary><see cref="ItemsSource"/> is observable collection.</summary>
        public bool IsObservableCollection
        {
            get { return (bool)GetValue(IsObservableCollectionProperty); }
            private set { SetValue(IsObservableCollectionPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey IsObservableCollectionPropertyKey =
            DependencyProperty.RegisterReadOnly(nameof(IsObservableCollection), typeof(bool), typeof(ProxyLastItem), new PropertyMetadata(false));
        /// <summary><see cref="DependencyProperty"/> для свойства <see cref="IsObservableCollection"/>.</summary>
        public static readonly DependencyProperty IsObservableCollectionProperty = IsObservableCollectionPropertyKey.DependencyProperty;



        /// <summary><see cref="ItemsSource"/> is BindingList.</summary>
        public bool IsBindingList
        {
            get { return (bool)GetValue(IsBindingListProperty); }
            private set { SetValue(IsBindingListPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey IsBindingListPropertyKey =
            DependencyProperty.RegisterReadOnly(nameof(IsBindingList), typeof(bool), typeof(ProxyLastItem), new PropertyMetadata(false));
        /// <summary><see cref="DependencyProperty"/> for property <see cref="IsBindingList"/>.</summary>
        public static readonly DependencyProperty IsBindingListProperty = IsBindingListPropertyKey.DependencyProperty;

        /// <summary>Last Item from <see cref="ItemsSource"/>.</summary>
        public object LastItem
        {
            get { return (object)GetValue(LastItemProperty); }
            private set { SetValue(LastItemPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey LastItemPropertyKey =
            DependencyProperty.RegisterReadOnly(nameof(LastItem), typeof(object), typeof(ProxyLastItem), new PropertyMetadata(null));
        /// <summary><see cref="DependencyProperty"/> for property <see cref="LastItem"/>.</summary>
        public static readonly DependencyProperty LastItemProperty = LastItemPropertyKey.DependencyProperty;


    }
}

最简单的解决方案是实施 IValueConverter(请参阅 Data conversion)。

PersonsToLastPersonNameConverter.cs

class PersonsToLastPersonNameConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    => value is IEnumerable collection 
      ? collection
        .Cast<Person>()
        .LastOrDefault() 
        .FullName
      : Binding.DoNothing;

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException();
}

用法

<Window>
  <Window.Resources>
    <PersonsToLastPersonNameConverter x:Key="PersonsToLastPersonNameConverter " />
  </Window.Resources>

  <TextBlock Text="{Binding Model.Persons, Converter={StaticResource PersonsToLastPersonNameConverter}"/>
</Window>

Model.cs

class Model : INotifyPropertyChanged
{
  public List<Person> Persons { get; }

  public Model() => this.Persons = new List<Person>();

  private void AddItem(Person person)
  {
    this.Persons.Add(person);

    // Trigger the converter
    OnPersonsChanged();
  }

  private void RemoveItem(int index)
  {
    this.Persons.RemoveAt(index);

    // Trigger the converter
    OnPersonsChanged();
  }

  // Trigger the converter
  private void OnPersonsChanged() => OnPropertyChanged(nameof(this.Persons));

  // TODO::Implement INotifyPropertyChanged
}

或者引入专用的 LastPerson 属性 作为绑定源。

如果您 必须通过 Getter 从 Xaml 导入最后一项,试试这个方法。 然而,添加 IValueConverter 或 属性 并不像添加它那么容易。

还有示例源码我放了

GitHub

使用xaml

<TextBlock Text="{Binding Users.FirstUser.Name}"/>
<TextBlock Text="{Binding Users.LastUser.Name}"/>

代码

public class UserItems : ObservableCollection<UserItem>, INotifyPropertyChanged
{
    private UserItem _firstItem;
    private UserItem _userItem;

    public UserItem FirstUser
    {
        get { return _firstItem; }
        set { _firstItem = value; OnPropertyChanged(); }
    }

    public UserItem LastUser
    {
        get { return _userItem; }
        set { _userItem = value; OnPropertyChanged(); }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        FirstUser = this.FirstOrDefault();
        LastUser = this.LastOrDefault();
    }

    public new event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

ViewModel

class MainViewModel
{
    public UserItems Users { get; set; }

    public MainViewModel()
    {
        Users = new UserItems
        {
            new UserItem { Name = "James" },
            new UserItem { Name = "Elena" }
        };
    }
}