更新的ItemsControl当在一个ObservableCollection的项被更新
问题:更新的ItemsControl当在一个ObservableCollection的项被更新
- 声明一个
ItemsControl
(或从ItemsControl
得到的控制)在 视图。 - 您将
ItemsControl.ItemsSource
属性绑定到ViewModel中的ObservableCollection
。 - 当您的物品被添加到/从
ObservableCollection
中删除时,您的视图会按预期更新。 - 但是,当您更改
ObservableCollection
中某个项目的属性时,该视图不会更新。
背景:
看来,这是一个普遍的问题很多WPF开发都遇到过。它已经被问了几次:
Notify ObservableCollection when Item changes
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
ObservableCollection and Item PropertyChanged
我的实现:
我试图实现Notify ObservableCollection when Item changes接受的解决方案。基本思路是在ObservableCollection
中的每个项目的MainWindowViewModel中连接一个PropertyChanged
处理程序。当一个项目的属性发生变化时,事件处理程序将被调用,并以某种方式更新视图。
我无法让实施工作。这是我的实现。
的ViewModels:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
项目视图模型:
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);
}
}
主窗口视图模型:
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");
}
}
查看:
个<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>
我的结果:
为了验证我的实现,我创建像这样一个观点。 TextBlock.Text
绑定到集合中的第一个项目。 ItemsControl
绑定到集合本身。
- 按下“添加员工”按钮,集合中增加了一个
EmployeeViewModel
对象,无论是TextBlock
和ItemsControl
按预期进行更新。 - 再次按下“添加员工”,
ItemsControl
会更新另一个条目。大! - 按下“递增员工年龄”按钮。每个项目的
Age
属性增加1.PropertyChanged
事件引发。调用ItemPropertyChanged
事件处理程序。按预期更新Textblock
。但是,ItemsControl
未更新。
我的印象是,当Employee.Age
根据Notify ObservableCollection when Item changes答案是改变ItemsControl
也应该被更新下。
我发现使用Snoop调试XAML的答案。
问题是,您尝试绑定到ToString()方法,并且不会引发PropertyChanged事件。如果你看看XAML绑定,你会注意到ObservableCollection实际上正在改变。
现在看每个项目的控制,它的文本在“文本”属性的绑定。没有,只是文字。
要解决这个问题简单地用一个包含你想显示的元素的DataTemplate中添加一个ItemsControl ItemTemplate中。
<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>
我们现在有一个绑定的绿灯。正在调用RaisePropertyChanged。
当当!
感谢您的回复。我为'Employees'属性(ObservableCollection)添加了一个setter,并在setter中调用了'RaisePropertyChanged'。但在按增量员工时间按钮时,它不会触发“ItemsControl”更新。 – 2015-02-10 04:52:48
很好的答案。我明白使用ItemTemplate绑定到该项目的属性可以解决此问题。事实上,如果您使用ItemTemplate,我们甚至需要处理CollectionChanged事件来将ItemPropertyChanged处理程序连接到每个项目。我认为可能有一种方法可以解决这个问题,而不使用ItemTemplate。如果没有提供其他答案。我会将你的标记标记为已接受。 – 2015-02-10 07:01:12
@FrankLiu声明一个ItemTemplate是在ItemsControl或派生控件(如ListBox)中可视化项目的标准方式。您可能需要阅读MSDN上的[数据模板概述](https://msdn.microsoft.com/en-us/library/ms742521.aspx)文章。 – Clemens 2015-02-10 08:11:47
你甚至想要做什么?可观察的集合是观察集合本身,而不是孩子的属性。如果孩子的财产发生变化,做一个收集变更的事件有什么好处? – michael 2015-02-10 20:45:51
@michael,我想要在集合中的项目更新时刷新ItemsControl。 – 2015-02-10 22:47:03
尽管如此,集合中的所有项目都会被计入。让用户界面获得该属性不会改变任何内容...参考依然如此。 – michael 2015-02-11 15:32:49