更新ObservableCollection中的项目时更新ItemsControl

时间:2015-02-10 01:55:17

标签: c# wpf mvvm observablecollection itemscontrol

问题:

  • 您在声明中声明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中实施已接受的解决方案。基本思想是在MainWindowViewModel中为PropertyChanged中的每个项目连接一个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));
    }
}

项目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);
    }
}

主窗口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");
    }
}

查看:

<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未更新。

我认为根据Notify ObservableCollection when Item changes中的答案更改ItemsControl时,Employee.Age也应该更新。

enter image description here

1 个答案:

答案 0 :(得分:10)

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

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

Snoop Showing Correct Binding

现在查看每个项目控件及其文本绑定在&#34;文本&#34;属性。没有,它只是文字。

Snoop showing no data binding to elements in the items control

要解决此问题,只需添加一个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>

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

Binding correctly shows green

钽哒!

Solution shown