更新ObservableCollection中的项目时更新ItemsControl

Fra*_*Liu 10 c# wpf itemscontrol observablecollection mvvm

问题:

  • 您在视图中声明ItemsControl(或从中派生的控件ItemsControl).
  • 您将ItemsControl.ItemsSource属性绑定到ObservableCollectionViewModel中的a.
  • 当项目被添加到/从中删除时,您的视图会按预期更新ObservableCollection.
  • 但是,当您更改项目中的项目的属性时,视图不会更新ObservableCollection.

背景:

这似乎是许多WPF开发人员遇到的常见问题.有人问过几次:

项目更改时通知ObservableCollection

ObservableCollection没有注意到它中的Item何时发生变化(即使使用INotifyPropertyChanged)

ObservableCollection和Item PropertyChanged

我的实施:

当Item更改时,我尝试在Notify ObservableCollection中实现接受的解决方案.基本思想是PropertyChanged在MainWindowViewModel中为每个项目连接一个处理程序ObservableCollection.更改项目的属性时,将调用事件处理程序,并以某种方式更新视图.

我无法让实现工作.这是我的实施.

的ViewModels:

class ViewModelBase : INotifyPropertyChanged 
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName = "")
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
Run Code Online (Sandbox Code Playgroud)

项目视图模型:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

主窗口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");
    }
}
Run Code Online (Sandbox Code Playgroud)

视图:

<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>
Run Code Online (Sandbox Code Playgroud)

我的结果:

为了验证我的实现,我创建了一个这样的视图.将TextBlock.Text被绑定到集合中的第一项.它ItemsControl与集合本身绑定在一起.

  • 按"添加员工"按钮,添加一个EmployeeViewModel集合中的对象,无论是TextBlockItemsControl按预期进行更新.
  • 再次按"添加员工",将ItemsControl使用另一个条目进行更新.大!
  • 按"递增员工年龄"按钮.Age每个项目的属性增加1. PropertyChanged事件被引发.该ItemPropertyChanged事件处理函数.该Textblock预期被更新.但是,ItemsControl没有更新.

我认为,当项目更改ItemsControl时,Employee.Age根据Notify ObservableCollection中的答案更改 时,应该更新.

在此输入图像描述

Azz*_*ziz 11

我找到了使用Snoop调试XAML 的答案.

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

窥探显示正确的绑定

现在查看每个项目控件及其在"Text"属性中的文本绑定.没有,它只是文字.

Snoop显示没有数据绑定到items控件中的元素

要解决此问题,只需添加一个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>
Run Code Online (Sandbox Code Playgroud)

我们现在对绑定有了一个绿灯.正在调用RaisePropertyChanged.

正确绑定显示绿色

当当!

解决方案显示

  • @FrankLiu声明ItemTemplate是可视化ItemsControl中的项目或ListBox等派生控件的标准方法.您可能需要阅读MSDN上的[数据模板概述](https://msdn.microsoft.com/en-us/library/ms742521.aspx)文章. (2认同)
  • @FrankLiu我觉得你们两个都很困惑,应该什么时候使用它们.DataTemplate可以简单地显示您要求XAML显示的内容.它不是为了确保在您的属性更新时它也将更新XAML.它并不关心这一点.至于INotifyPropertyChanged的用法,你不一定需要它.只有当其他人修改该属性时,确保它会在UI中更改时,您才需要它.看[这里](http://stackoverflow.com/questions/10475130/why-use-inotifypropertychanged-with-bindings-in-wpf). (2认同)