的ObservableCollection

时间:2010-09-17 00:40:34

标签: c# wpf observablecollection

我有一个绑定到ObservableCollection<MyEntity>类型列表的WPF对话框。在对话框中,我希望只有在对ObservableCollection<MyEntity>列表进行更改时才启用“确定”按钮 - 包括从列表中添加/删除项目以及修改列表中的各个项目。

为了从列表中添加/删除项目,很容易 - 我为CollectionChanged事件实现了一个处理程序。

我不知道如何修改单个项目。比方说,MyEntity.Name =“New Value”,MyEntity类需要实现哪些接口才能使其“可观察”?

4 个答案:

答案 0 :(得分:9)

MyEntity需要实现INotifyPropertyChanged,然后在发生属性更改时触发PropertyChanged事件。像这样:

public class MyEntity : INotifyPropertyChanged
{
    public bool MyFlag 
    {
        get { return _myFlag; }
        set 
        {
            _myFlag = value;
            OnPropertyChanged("MyFlag");
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

解决这个问题的两种方法是:

  • 在对象内部有一个事件侦听器,然后在属性更改时设置IsDirty标志。然后OK按钮绑定到命令(检查ICommand接口的用法),并在命令的CanExecute方法中检查ObservableCollection中的任何对象是否已设置为脏。可以使用简单的LINQ语句完成此检查:myCollection.Any(x => x.IsDirty == true)

  • 这个方法更笨拙和臭...有一个外部对象侦听更改(通过在每个对象上订阅PropertyChanged事件),然后外部侦听器可以启用OK按钮(通过数据绑定或通过直接设置)。

答案 1 :(得分:0)

我喜欢slugster提供的答案,这里是一个关于slugster答案的替代建筑。

如果使用DelegateCommnd绑定到OK按钮,则可以为CollectionChanged和PropertyChanged添加事件处理程序,以更改一个简单的布尔标志来控制OK按钮的状态。

public class MainViewModel : ViewModelBase
{
  public DelegateCommand<object> RunCommand { get; set; }
  public DelegateCommand<object> OkCommand { get; set; }
  private bool enableOk = false;
  private bool setOK = false;
  private ObservableCollection<MyEntity> _entites = new ObservableCollection<MyEntity>();

  public MainViewModel()
  {
     _entites.CollectionChanged += (s, e) =>
     {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
           // handle property changing
           foreach (MyEntity item in e.NewItems)
           {
              ((INotifyPropertyChanged)item).PropertyChanged += (s1, e1) => { if (setOK) enableOk = true; };
           }
        }
        // handle collection changing
        if (setOK) enableOk = false;
     };

     MyEntity me1 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
     MyEntity me2 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
     MyEntity me3 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
     _entites.Add(me1);
     _entites.Add(me2);
     _entites.Add(me3);

     // allow collection changes now to start enabling the ok button...
     setOK = true;

     RunCommand = new DelegateCommand<object>(OnRunCommnad, CanRunCommand);
     OkCommand = new DelegateCommand<object>(OnOkCommnad, CanOkCommand);
  }

  private void OnRunCommnad(object obj)
  {
     MyEntity me = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };

     // causes ok to become enabled
     _entites.Add(me);

     MyEntity first = _entites[0];

     // causes ok to become enabled
     first.Name = "Zamboni";
  }

  private bool CanRunCommand(object obj)
  {
     return true;
  }

  private void OnOkCommnad(object obj)
  {
  }

  private bool CanOkCommand(object obj)
  {
     return enableOk;
  } 
}

这是MyEntity版本(类似于slugster提供的版本):
在此示例中,只有Name属性触发事件...

public class MyEntity : INotifyPropertyChanged
{
  private string _name = string.Empty;
  public string Name
  { 
     get
     {
        return _name;
     }
     set
     {
        _name = value;
        OnPropertyChanged("Name");
     }
  }
  public string Information { get; set; }
  public string Details { get; set; }

  public event PropertyChangedEventHandler PropertyChanged;

  protected void OnPropertyChanged(string propertyName)
  {
     PropertyChangedEventHandler handler = PropertyChanged;

     if (handler != null)
     {
        handler(this, new PropertyChangedEventArgs(propertyName));
     }
  }
}

答案 2 :(得分:0)

您应该实施INotifyPropertyChanged。你可以通过以下方式完成 (如您所见,此实现完全是线程安全的)

private readonly object _sync = new object();

public event PropertyChangedEventHandler PropertyChanged
{
   add { lock (_sync) _propertyChanged += value; }
   remove { lock (_sync) _propertyChanged -= value; }
} private PropertyChangedEventHandler _propertyChanged;

protected void OnPropertyChanged(Expression<Func<object>> propertyExpression)
{
   OnPropertyChanged(GetPropertyName(propertyExpression));
}

protected string GetPropertyName(Expression<Func<object>> propertyExpression)
{
    MemberExpression body;

    if (propertyExpression.Body is UnaryExpression)
        body = (MemberExpression) ((UnaryExpression) propertyExpression.Body).Operand;
    else
        body = (MemberExpression) propertyExpression.Body;

    return body.Member.Name;
}

protected virtual void OnPropertyChanged(string propertyName)
{
  var handler = _propertyChanged;
  if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}

按照上面描述的实施,您可以通过两种方式通知您的更改 1)第一种方式

public int MyProperty
{
     get { return _myProperty; }
     set
        {
           if (value != __myProperty)
           {
               _subVersion = value;
               OnPropertyChanged(MyPropertyPropertyName);
            }
        }
} private int _myProperty; const string MyPropertyPropertyName = "MyProperty";

2)第二种方式

public int MyProperty
{
     get { return _myProperty; }
     set
        {
           if (value != _myProperty)
           {
               _subVersion = value;
               OnPropertyChanged(() => MyProperty);
            }
        }
} private int _myProperty; 

答案 3 :(得分:0)

另一个解决方案可能是需要项目实现INotifyPropertyChanged的自定义可观察集合。用户必须将处理程序附加到OnItemPropertyChanged事件,只要集合中项目的属性发生更改,就会调用该事件。

public class ObservableCollectionEnhanced<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
  public ObservableCollectionEnhanced()
    : base()
  { }

  public ObservableCollectionEnhanced(IEnumerable<T> collection)
    : base(collection)
  {
    foreach (T item in Items)
      item.PropertyChanged += OnItemPropertyChanged;
  }

  public ObservableCollectionEnhanced(List<T> list)
    : base(list)
  {
    foreach (T item in Items)
      item.PropertyChanged += OnItemPropertyChanged;
  }

  public event System.ComponentModel.PropertyChangedEventHandler ItemPropertyChanged;
  public void OnItemPropertyChanged(Object sender, PropertyChangedEventArgs e)
  {
    if (null != ItemPropertyChanged)
      ItemPropertyChanged(sender, e);
  }

  protected override void InsertItem(int index, T item)
  {
    base.InsertItem(index, item);
    item.PropertyChanged += OnItemPropertyChanged;
  }

  protected override void RemoveItem(int index)
  {
    T item = this.Items[index];
    item.PropertyChanged -= OnItemPropertyChanged;
    base.RemoveItem(index);
  }

  protected override void SetItem(int index, T item)
  {
    T oldItem = Items[index];
    base.SetItem(index, item);
    oldItem.PropertyChanged -= OnItemPropertyChanged;
    item.PropertyChanged += OnItemPropertyChanged;
  }
}

按如下方式配置处理程序:

public void OnItemPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
  System.Diagnostics.Debug.WriteLine("Update called on {0}", sender);
}

...

collection.ItemPropertyChanged += OnItemPropertyChanged;