更新的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对象,无论是TextBlockItemsControl按预期进行更新。
  • 再次按下“添加员工”,ItemsControl会更新另一个条目。大!
  • 按下“递增员工年龄”按钮。每个项目的Age属性增加1. PropertyChanged事件引发。调用ItemPropertyChanged事件处理程序。按预期更新Textblock。但是,ItemsControl未更新。

我的印象是,当Employee.Age根据Notify ObservableCollection when Item changes答案是改变ItemsControl也应该被更新下。

enter image description here

+0

你甚至想要做什么?可观察的集合是观察集合本身,而不是孩子的属性。如果孩子的财产发生变化,做一个收集变更的事件有什么好处? – michael 2015-02-10 20:45:51

+0

@michael,我想要在集合中的项目更新时刷新ItemsControl。 – 2015-02-10 22:47:03

+0

尽管如此,集合中的所有项目都会被计入。让用户界面获得该属性不会改变任何内容...参考依然如此。 – michael 2015-02-11 15:32:49

我发现使用Snoop调试XAML的答案。

问题是,您尝试绑定到ToString()方法,并且不会引发PropertyChanged事件。如果你看看XAML绑定,你会注意到ObservableCollection实际上正在改变。

Snoop Showing Correct Binding

现在看每个项目的控制,它的文本在“文本”属性的绑定。没有,只是文字。

Snoop showing no data binding to elements in the items control

要解决这个问题简单地用一个包含你想显示的元素的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。

Binding correctly shows green

当当!

Solution shown

+0

感谢您的回复。我为'Employees'属性(ObservableCollection)添加了一个setter,并在setter中调用了'RaisePropertyChanged'。但在按增量员工时间按钮时,它不会触发“ItemsControl”更新。 – 2015-02-10 04:52:48

+0

很好的答案。我明白使用ItemTemplate绑定到该项目的属性可以解决此问题。事实上,如果您使用ItemTemplate,我们甚至需要处理CollectionChanged事件来将ItemPropertyChanged处理程序连接到每个项目。我认为可能有一种方法可以解决这个问题,而不使用ItemTemplate。如果没有提供其他答案。我会将你的标记标记为已接受。 – 2015-02-10 07:01:12

+2

@FrankLiu声明一个ItemTemplate是在ItemsControl或派生控件(如ListBox)中可视化项目的标准方式。您可能需要阅读MSDN上的[数据模板概述](https://msdn.microsoft.com/en-us/library/ms742521.aspx)文章。 – Clemens 2015-02-10 08:11:47