[英]Update ItemsControl when an item in an ObservableCollection is updated
The Problem:问题:
ItemsControl
( or a control derived from ItemsControl
) in the view.您在视图中声明ItemsControl
(或从ItemsControl
派生的控件)。ItemsControl.ItemsSource
property to an ObservableCollection
in your ViewModel.您将ItemsControl.ItemsSource
属性绑定到 ViewModel 中的ObservableCollection
。ObservableCollection
.当一个项目被添加到ObservableCollection
/从ObservableCollection
删除时,您的视图会按预期更新。ObservableCollection
.但是,当您更改ObservableCollection
项目的属性时,视图不会更新。Background:背景:
It seems that this is a common problem many WPF developers have encountered.看来这是很多WPF开发者都遇到过的常见问题。 It has been asked a few times:已经被问过几次了:
Notify ObservableCollection when Item changes 当 Item 改变时通知 ObservableCollection
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged) ObservableCollection 不会注意到其中的 Item 何时更改(即使使用 INotifyPropertyChanged)
ObservableCollection and Item PropertyChanged ObservableCollection 和 Item PropertyChanged
My Implementation:我的实现:
I tried to implement the accepted solution in Notify ObservableCollection when Item changes . 当 Item changes 时,我尝试在Notify ObservableCollection 中实现公认的解决方案。 The basic idea is to hook up a PropertyChanged
handler in your MainWindowViewModel for each item in the ObservableCollection
.基本思想是在 MainWindowViewModel 中为ObservableCollection
每个项目连接一个PropertyChanged
处理程序。 When an item's property is changed, the event handler will be invoked and somehow the View is updated.当项目的属性发生更改时,将调用事件处理程序并以某种方式更新视图。
I could not get the implementation to work.我无法使实现工作。 Here is my implementation.这是我的实现。
ViewModels:视图模型:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Item ViewModel:项目视图模型:
class EmployeeViewModel : ViewModelBase
{
private int _age;
private string _name;
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged("Age");
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public override string ToString()
{
return string.Format("{0} is {1} years old", Name, Age);
}
}
Main Window ViewModel:主窗口视图模型:
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<EmployeeViewModel> _collection;
public MainWindowViewModel()
{
_collection = new ObservableCollection<EmployeeViewModel>();
_collection.CollectionChanged += MyItemsSource_CollectionChanged;
AddEmployeeCommand = new DelegateCommand(() => AddEmployee());
IncrementEmployeeAgeCommand = new DelegateCommand(() => IncrementEmployeeAge());
}
public ObservableCollection<EmployeeViewModel> Employees
{
get { return _collection; }
}
public ICommand AddEmployeeCommand { get; set; }
public ICommand IncrementEmployeeAgeCommand { get; set; }
public void AddEmployee()
{
_collection.Add(new EmployeeViewModel()
{
Age = 1,
Name = "Random Joe",
});
}
public void IncrementEmployeeAge()
{
foreach (var item in _collection)
{
item.Age++;
}
}
private void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (EmployeeViewModel item in e.NewItems)
item.PropertyChanged += ItemPropertyChanged;
if (e.OldItems != null)
foreach (EmployeeViewModel item in e.OldItems)
item.PropertyChanged -= ItemPropertyChanged;
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged("Employees");
}
}
View:看法:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:d="clr-namespace:Iress.IosPlus.DynamicOE.Controls"
Title="MainWindow" Height="350" Width="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.7*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Command="{Binding AddEmployeeCommand}">Add Employee</Button>
<Button Command="{Binding IncrementEmployeeAgeCommand}">Increment Employee Age</Button>
</StackPanel>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=Employees[0]}"></TextBlock>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees}" BorderBrush="Red" BorderThickness="1"></ItemsControl>
</Grid>
</Grid>
My Results:我的结果:
To verify my implementation, I create a view like so.为了验证我的实现,我创建了一个这样的视图。 The TextBlock.Text
is bound to the first item in the collection. TextBlock.Text
绑定到集合中的第一项。 The ItemsControl
is bound to the collection itself. ItemsControl
绑定到集合本身。
EmployeeViewModel
object in the collection and both the TextBlock
and ItemsControl
are updated as expected.按“添加员工”按钮会在集合中添加一个EmployeeViewModel
对象,并且TextBlock
和ItemsControl
都按预期更新。ItemsControl
is updated with another entry.再次按下“添加员工”, ItemsControl
将更新为另一个条目。 Great!伟大的!Age
property of each item is incremented by 1. The PropertyChanged
event is raised.每个项目的Age
属性递增 1。引发PropertyChanged
事件。 The ItemPropertyChanged
event handler is invoked. ItemPropertyChanged
事件处理程序被调用。 The Textblock
is updated as expected. Textblock
按预期更新。 However, the ItemsControl
is not updated.但是, ItemsControl
不会更新。 I am under the impression that the ItemsControl
should be updated too when the Employee.Age
is changed according to the answer in Notify ObservableCollection when Item changes .我的印象是,当Employee.Age
根据Notify ObservableCollection 中的答案更改时, ItemsControl
应该更新。
I found the answer using Snoop to debug XAML.我找到了使用Snoop调试 XAML 的答案。
The issue is that you are trying to bind to the ToString() method and that does not raise the PropertyChanged event.问题是您正在尝试绑定到 ToString() 方法并且不会引发 PropertyChanged 事件。 If you look at the XAML bindings you will notice that the ObservableCollection is actually changing.如果您查看 XAML 绑定,您会注意到 ObservableCollection 实际上正在发生变化。
Now look at each item control and it's texts binding in the "Text" property.现在查看每个项目控件及其在“Text”属性中的文本绑定。 There are none, it's just text.没有,它只是文本。
To fix this simply add an ItemsControl ItemTemplate with a DataTemplate that contains the elements you'd like to be displayed.要解决这个问题,只需添加一个 ItemsControl ItemTemplate 和一个包含您想要显示的元素的 DataTemplate。
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees, UpdateSourceTrigger=PropertyChanged}" BorderBrush="Red" BorderThickness="1" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat=" {0} is {1} years old">
<Binding Path="Name"/>
<Binding Path="Age"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
We now have a green light on binding.我们现在有一个关于绑定的绿灯。 RaisePropertyChanged is being called.正在调用 RaisePropertyChanged。
Ta-da!达达!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.