连接CollectionChanged和PropertyChanged(或:为什么某些WPF绑定不刷新?) [英] Wiring up CollectionChanged and PropertyChanged (Or : Why do some WPF Bindings not refresh?)

查看:89
本文介绍了连接CollectionChanged和PropertyChanged(或:为什么某些WPF绑定不刷新?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

WPF DataBindings曾经让我高兴.我现在偶然发现的一件事是,在某些时候它们只是没有按计划刷新.请查看以下(非常简单的)代码:

WPF DataBindings used to make me happy. One thing I've stumbled over just now is that at some point they just don't refresh as intented. Please take a look at the following (fairly simple) code:

<Window x:Class="CVFix.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="300">
  <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition Height="40"></RowDefinition>
    </Grid.RowDefinitions>
    <ListBox Grid.Row="0" Grid.Column="0" 
                  ItemsSource="{Binding Path=Persons}"
                  SelectedItem="{Binding Path=SelectedPerson}"
                  x:Name="lbPersons"></ListBox>
    <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Path=SelectedPerson.Name, UpdateSourceTrigger=PropertyChanged}"/>
  </Grid>
</Window>

XAML背后的代码:

The Code behind for the XAML:

using System.Windows;
namespace CVFix
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public ViewModel Model { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        this.Model = new ViewModel();
        this.DataContext = this.Model;
    }
  }
}

最后,这是ViewModel类:

Finally, here's the ViewModel classes:

using System.Collections.ObjectModel;
using System.ComponentModel;

namespace CVFix
{
  public class ViewModel : INotifyPropertyChanged
  {
    private PersonViewModel selectedPerson;

    public PersonViewModel SelectedPerson
    {
        get { return this.selectedPerson; }
        set
        {
            this.selectedPerson = value;

            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
        }
    }

    public ObservableCollection<PersonViewModel> Persons { get; set; }

    public ViewModel()
    {
        this.Persons = new ObservableCollection<PersonViewModel>();
        this.Persons.Add(new PersonViewModel() { Name = "Adam" });
        this.Persons.Add(new PersonViewModel() { Name = "Bobby" });
        this.Persons.Add(new PersonViewModel() { Name = "Charles" });
    }

    public event PropertyChangedEventHandler PropertyChanged;
  }
}

public class PersonViewModel : INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return this.name; }
        set
        {
            this.name = value;
            if(this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }

    public override string ToString()
    {
        return this.Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

我想发生的事情:当我从列表框中选择一个条目并在文本框中修改其名称时,该列表就会更新以显示新值.

What I'd want to happen: As I select an entry from the ListBox and modify its Name in the TextBox, the list gets updated to display the new value.

发生了什么:什么都没有.如果我是法官,那就是正确的行为. 我确保已触发SelectedItem的PropertyChanged,但这(当然)不会导致CollectionChanged被触发.

What happens : nothing. And that is the correct behaviour, If I'm any judge. I made sure that the SelectedItem's PropertyChanged is fired, but that does (of course) not cause CollectionChanged to be fired.

要解决此问题,我创建了一个ObservableCollection派生的类,该类具有公共的OnCollectionChanged方法,请参见此处:

To fix this, I created an ObservableCollection-derived class that has a public OnCollectionChanged method, see here :

public class PersonList : ObservableCollection<PersonViewModel>
{
    public void OnCollectionChanged()
    {
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ));
    }
}

我从ViewModel的构造函数访问它,如下所述:

I access this from the ViewModel's constructor, as described below:

    public ViewModel()
    {
        PersonViewModel vm1 = new PersonViewModel()
        {
            Name = "Adam"
        };
        PersonViewModel vm2 = new PersonViewModel()
        {
            Name = "Bobby"
        };
        PersonViewModel vm3 = new PersonViewModel()
        {
            Name = "Charles"
        };
        vm1.PropertyChanged += this.PersonChanged;

        this.Persons = new PersonList();


        this.Persons.Add(vm1);
        this.Persons.Add(vm2);
        this.Persons.Add(vm3);
    }

    void PersonChanged(object sender, PropertyChangedEventArgs e)
    {
        this.Persons.OnCollectionChanged();
    }

它可以工作,但这不是一个干净的解决方案.我的下一个想法是创建一个ObservableCollection的派生模型,该模型在CollectionChanged-handler中自动进行接线.

It works, but it's not a clean solution. My next idea would be creating a derivate of ObservableCollection that does the wiring up automatically in a CollectionChanged-handler.

public class SynchronizedObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                {
                    foreach (INotifyPropertyChanged item in e.NewItems)
                    {
                        item.PropertyChanged += this.ItemChanged;
                    }
                    break;
                }

            case NotifyCollectionChangedAction.Remove:
                {
                    foreach (INotifyPropertyChanged item in e.OldItems)
                    {
                        item.PropertyChanged -= this.ItemChanged;
                    }
                    break;
                }
        }
        base.OnCollectionChanged(e);
    }

    void ItemChanged(object sender, PropertyChangedEventArgs e)
    {
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

问题是:还有更好的方法吗?这真的有必要吗?

The question is : is there a better way to do this? Is this really necessary?

非常感谢您的输入!

推荐答案

否,完全没有必要.您的样本失败的原因很微妙,但很简单.

No, it's not necessary at all. The reason your sample is failing is subtle, but quite simple.

如果不为WPF提供数据项的模板(例如列表中的Person对象),则默认使用ToString()方法显示.那是一个成员,而不是属性,因此当值更改时,您不会收到任何事件通知.

If you don't provide WPF with a template for a data item (such as the Person objects in your list), it'll default to using the ToString() method to display. That's a member, not a property, and so you get no event notification when the value changes.

如果将DisplayMemberPath="Name"添加到列表框中,它将生成一个模板,该模板可以正确地绑定到您的个人的Name,然后模板将按照您的期望自动更新.

If you add DisplayMemberPath="Name" to your listbox, it'll generate a template that binds properly to the Name of your person - which will then update automatically as you'd expect.

这篇关于连接CollectionChanged和PropertyChanged(或:为什么某些WPF绑定不刷新?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆