wpf 当 ObservableCollection 中的项目更新时更新 ItemsControl

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28422800/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 12:56:05  来源:igfitidea点击:

Update ItemsControl when an item in an ObservableCollection is updated

c#wpfmvvmobservablecollectionitemscontrol

提问by Frank Liu

The Problem:

问题:

  • You declare an ItemsControl( or a control derived from ItemsControl) in the view.
  • You bind the ItemsControl.ItemsSourceproperty to an ObservableCollectionin your ViewModel.
  • Your view updates as expected when an item is added to /removed from the ObservableCollection.
  • BUT, the view does not update when you change a property of an item in the ObservableCollection.
  • 您在视图中声明一个ItemsControl(或派生自 的控件ItemsControl)。
  • 您将ItemsControl.ItemsSource属性绑定到ObservableCollectionViewModel 中的 。
  • 当一个项目被添加到/从ObservableCollection.
  • 但是,当您更改ObservableCollection.

Background:

背景:

It seems that this is a common problem many WPF developers have encountered. It has been asked a few times:

看来这是很多WPF开发者都遇到过的常见问题。已经被问过几次了:

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. The basic idea is to hook up a PropertyChangedhandler in your MainWindowViewModel for each item in the ObservableCollection. When an item's property is changed, the event handler will be invoked and somehow the View is updated.

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

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.Textis bound to the first item in the collection. The ItemsControlis bound to the collection itself.

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

  • Pressing the "Add Employee" button adds an EmployeeViewModelobject in the collection and both the TextBlockand ItemsControlare updated as expected.
  • Pressing the "Add Employee" again, the ItemsControlis updated with another entry. Great!
  • Pressing the "Increment Employee Age" button. The Ageproperty of each item is incremented by 1. The PropertyChangedevent is raised. The ItemPropertyChangedevent handler is invoked. The Textblockis updated as expected. However, the ItemsControlis not updated.
  • 按“添加员工”按钮EmployeeViewModel在集合中添加一个对象,并且TextBlockItemsControl都按预期更新。
  • 再次按下“添加员工”,将ItemsControl更新为另一个条目。伟大的!
  • 按“增加员工年龄”按钮。Age每个项目的属性增加 1。PropertyChanged引发事件。该ItemPropertyChanged事件处理函数。该Textblock预期被更新。但是,ItemsControl没有更新。

I am under the impression that the ItemsControlshould be updated too when the Employee.Ageis changed according to the answer in Notify ObservableCollection when Item changes.

我的印象是,当 Item 更改时根据Notify ObservableCollection 中的答案更改ItemsControl时,Employee.Age也 应该更新。

enter image description here

在此处输入图片说明

回答by AzzamAziz

I found the answer using Snoopto 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. If you look at the XAML bindings you will notice that the ObservableCollection is actually changing.

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

Snoop Showing Correct Binding

Snoop 显示正确绑定

Now look at each item control and it's texts binding in the "Text" property. There are none, it's just text.

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

Snoop showing no data binding to elements in the items control

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

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。

Binding correctly shows green

绑定正确显示绿色

Ta-da!

达达!

Solution shown

解决方案显示