如何绑定到列表中的最后一项?
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 或 属性 并不像添加它那么容易。
还有示例源码我放了
使用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" }
};
}
}
在 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 或 属性 并不像添加它那么容易。
还有示例源码我放了
使用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" }
};
}
}