连接CollectionChanged和PropertyChanged(或:为什么某些WPF绑定不刷新?) [英] Wiring up CollectionChanged and PropertyChanged (Or : Why do some WPF Bindings not refresh?)
问题描述
WPF DataBindings曾经让我高兴.我现在偶然发现的一件事是,在某些时候它们只是没有按计划刷新.请查看以下(非常简单的)代码:
WPF DataBindings used to make me happy. One thing I've stumbled over just now is that at some point they just don't refresh as intented. Please take a look at the following (fairly simple) code:
<Window x:Class="CVFix.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding Path=Persons}"
SelectedItem="{Binding Path=SelectedPerson}"
x:Name="lbPersons"></ListBox>
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Path=SelectedPerson.Name, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
XAML背后的代码:
The Code behind for the XAML:
using System.Windows;
namespace CVFix
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ViewModel Model { get; set; }
public MainWindow()
{
InitializeComponent();
this.Model = new ViewModel();
this.DataContext = this.Model;
}
}
}
最后,这是ViewModel类:
Finally, here's the ViewModel classes:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace CVFix
{
public class ViewModel : INotifyPropertyChanged
{
private PersonViewModel selectedPerson;
public PersonViewModel SelectedPerson
{
get { return this.selectedPerson; }
set
{
this.selectedPerson = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
}
}
public ObservableCollection<PersonViewModel> Persons { get; set; }
public ViewModel()
{
this.Persons = new ObservableCollection<PersonViewModel>();
this.Persons.Add(new PersonViewModel() { Name = "Adam" });
this.Persons.Add(new PersonViewModel() { Name = "Bobby" });
this.Persons.Add(new PersonViewModel() { Name = "Charles" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
public class PersonViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public override string ToString()
{
return this.Name;
}
public event PropertyChangedEventHandler PropertyChanged;
}
我想发生的事情:当我从列表框中选择一个条目并在文本框中修改其名称时,该列表就会更新以显示新值.
What I'd want to happen: As I select an entry from the ListBox and modify its Name in the TextBox, the list gets updated to display the new value.
发生了什么:什么都没有.如果我是法官,那就是正确的行为. 我确保已触发SelectedItem的PropertyChanged,但这(当然)不会导致CollectionChanged被触发.
What happens : nothing. And that is the correct behaviour, If I'm any judge. I made sure that the SelectedItem's PropertyChanged is fired, but that does (of course) not cause CollectionChanged to be fired.
要解决此问题,我创建了一个ObservableCollection派生的类,该类具有公共的OnCollectionChanged方法,请参见此处:
To fix this, I created an ObservableCollection-derived class that has a public OnCollectionChanged method, see here :
public class PersonList : ObservableCollection<PersonViewModel>
{
public void OnCollectionChanged()
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ));
}
}
我从ViewModel的构造函数访问它,如下所述:
I access this from the ViewModel's constructor, as described below:
public ViewModel()
{
PersonViewModel vm1 = new PersonViewModel()
{
Name = "Adam"
};
PersonViewModel vm2 = new PersonViewModel()
{
Name = "Bobby"
};
PersonViewModel vm3 = new PersonViewModel()
{
Name = "Charles"
};
vm1.PropertyChanged += this.PersonChanged;
this.Persons = new PersonList();
this.Persons.Add(vm1);
this.Persons.Add(vm2);
this.Persons.Add(vm3);
}
void PersonChanged(object sender, PropertyChangedEventArgs e)
{
this.Persons.OnCollectionChanged();
}
它可以工作,但这不是一个干净的解决方案.我的下一个想法是创建一个ObservableCollection的派生模型,该模型在CollectionChanged-handler中自动进行接线.
It works, but it's not a clean solution. My next idea would be creating a derivate of ObservableCollection that does the wiring up automatically in a CollectionChanged-handler.
public class SynchronizedObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
foreach (INotifyPropertyChanged item in e.NewItems)
{
item.PropertyChanged += this.ItemChanged;
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
foreach (INotifyPropertyChanged item in e.OldItems)
{
item.PropertyChanged -= this.ItemChanged;
}
break;
}
}
base.OnCollectionChanged(e);
}
void ItemChanged(object sender, PropertyChangedEventArgs e)
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
问题是:还有更好的方法吗?这真的有必要吗?
The question is : is there a better way to do this? Is this really necessary?
非常感谢您的输入!
推荐答案
否,完全没有必要.您的样本失败的原因很微妙,但很简单.
No, it's not necessary at all. The reason your sample is failing is subtle, but quite simple.
如果不为WPF提供数据项的模板(例如列表中的Person
对象),则默认使用ToString()
方法显示.那是一个成员,而不是属性,因此当值更改时,您不会收到任何事件通知.
If you don't provide WPF with a template for a data item (such as the Person
objects in your list), it'll default to using the ToString()
method to display. That's a member, not a property, and so you get no event notification when the value changes.
如果将DisplayMemberPath="Name"
添加到列表框中,它将生成一个模板,该模板可以正确地绑定到您的个人的Name
,然后模板将按照您的期望自动更新.
If you add DisplayMemberPath="Name"
to your listbox, it'll generate a template that binds properly to the Name
of your person - which will then update automatically as you'd expect.
这篇关于连接CollectionChanged和PropertyChanged(或:为什么某些WPF绑定不刷新?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!