【问题标题】:Update ItemsControl when an item in an ObservableCollection is updated当 ObservableCollection 中的项目更新时更新 ItemsControl
【发布时间】:2015-04-09 23:04:48
【问题描述】:

问题:

  • 您在 查看。
  • 您将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 中为ObservableCollection 中的每个项目连接一个PropertyChanged 处理程序。当一个项目的属性发生变化时,事件处理程序将被调用,并以某种方式更新视图。

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

视图模型:

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也应该更新。

【问题讨论】:

  • 你到底想做什么?可观察集合是观察集合本身,而不是子对象的属性。如果子属性发生更改,执行集合更改事件有什么好处?
  • @michael,我希望 ItemsControl 在集合中的项目更新时刷新。
  • 那会完成什么,集合中的所有项目都被考虑在内。让 UI 获取属性不会改变任何东西......引用仍然相同。

标签: c# wpf mvvm observablecollection itemscontrol


【解决方案1】:

我使用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更新。
  • 很好的答案。我了解使用 ItemTemplate 绑定到项目的属性可以解决此问题。事实上,如果您使用 ItemTemplate,我们甚至需要处理 CollectionChanged 事件来将 ItemPropertyChanged 处理程序连接到每个项目。我认为可能有一种方法可以在不使用 ItemTemplate 的情况下解决此问题。如果没有其他答案。我会将您的标记为已接受。
  • @FrankLiu 声明 ItemTemplate 是在 ItemsControl 或 ListBox 等派生控件中可视化项目的标准方法。您可能想阅读 MSDN 上的 Data Templating Overview 文章。
  • 好眼光,我什至没有注意到 OP 是依靠 .ToString() 来显示该项目的!你是对的,正确的做法是创建一个 Template 来显示项目,并将 UI 元素绑定到属性,以便它们对更改通知做出反应。 @FrankLiu 如果您想避免为 ItemsControl 定义 ItemTemplate,您也可以改用隐式 DataTemplate。这是一种告诉 WPF 任何时候在 Visual Tree 中遇到 EmployeeViewModel 类型的项目时,它应该使用指定的模板而不是默认的 .ToString() 来呈现它
  • @FrankLiu 我认为您混淆了两者以及何时应该使用它们。 DataTemplate 用于简单地显示您要求 XAML 显示的内容。它不能确保当您的属性更新时,它也会更新 XAML。它不在乎那个。至于INotifyPropertyChanged的用法,你不一定需要。只有当其他人修改该属性时,您才需要确保它会在 UI 中发生变化。看here
猜你喜欢
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-05
  • 1970-01-01
  • 1970-01-01
  • 2014-01-29
相关资源
最近更新 更多