简体   繁体   English

的ObservableCollection

[英]ObservableCollection

I have a WPF dialog that is bound to a list of ObservableCollection<MyEntity> type. 我有一个绑定到ObservableCollection<MyEntity>类型列表的WPF对话框。 In the dialog, I want the "OK" button to be enabled only if changes are made to the ObservableCollection<MyEntity> list - that includes adding/removing items from the list and modifying the individual items in the list. 在对话框中,我希望只有在对ObservableCollection<MyEntity>列表进行更改时才启用“确定”按钮 - 包括从列表中添加/删除项目以及修改列表中的各个项目。

For adding/removing items from the list, it is easy - I implemented a handler for the CollectionChanged event. 为了从列表中添加/删除项目,很容易 - 我为CollectionChanged事件实现了一个处理程序。

What I don't know how to do is when an individual item is modified. 我不知道怎么办是修改单个项目。 Say, MyEntity.Name="New Value", what interface does MyEntity class need to implement to make it 'observable'? 比方说,MyEntity.Name =“New Value”,MyEntity类需要实现哪些接口才能使其“可观察”?

MyEntity needs to implement INotifyPropertyChanged, then when a property change occurs you fire the PropertyChanged event. MyEntity需要实现INotifyPropertyChanged,然后在发生属性更改时触发PropertyChanged事件。 Like this: 像这样:

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;
}

Two ways to approach this are: 解决这个问题的两种方法是:

  • have an event listener internal to the object which then sets an IsDirty flag whenever a property changes. 在对象内部有一个事件监听器,然后在属性发生变化时设置一个IsDirty标志。 Then OK button is bound to a command (check out the usage of the ICommand interface), and in the CanExecute method of the command you check if any of the objects in the ObservableCollection have been set to dirty. 然后将OK按钮绑定到命令(检查ICommand接口的用法),并在命令的CanExecute方法中检查ObservableCollection中的任何对象是否已设置为dirty。 This check can be done with a simple LINQ statement: myCollection.Any(x => x.IsDirty == true) 可以使用简单的LINQ语句完成此检查: myCollection.Any(x => x.IsDirty == true)

  • this method is more clunky and smelly.... have an external object listening for changes (by subscribing to the PropertyChanged event on each object), and that external listener can then enable the OK button (via databinding or by setting it directly). 这个方法更笨重和臭...有一个外部对象侦听更改(通过在每个对象上订阅PropertyChanged事件),然后外部侦听器可以启用OK按钮(通过数据绑定或直接设置)。

I like the answer provided by slugster, here is an alternative building on slugster's answer. 我喜欢slugster提供的答案,这里是一个关于slugster答案的替代建筑。

If you bind to your OK button using DelegateCommnd you can add event handlers for CollectionChanged and PropertyChanged to change a simple boolean flag to control the state of the OK button. 如果使用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;
  } 
}

Here is a version MyEntity (similar to the one provided by slugster): 这是MyEntity版本(类似于slugster提供的版本):
Only the Name property fires an event in this example... 在此示例中,只有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));
     }
  }
}

You should implement INotifyPropertyChanged . 您应该实现INotifyPropertyChanged You could do it by the following way (as you can see, this implementation is fully thread safe) 您可以通过以下方式执行此操作(如您所见,此实现完全是线程安全的)

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

Following the implementation I described above, you can notify about your changes by two ways 1) The first way 在我上面描述的实现之后,您可以通过两种方式通知您的更改1)第一种方式

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

2) And the second way 2)第二种方式

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

Another solution could be a custom observable collection that requires items to implement INotifyPropertyChanged . 另一个解决方案可能是自定义可观察集合,需要项目来实现INotifyPropertyChanged The user must attach a handler to the OnItemPropertyChanged event, which will be called whenever the property of an item in the collection is changed. 用户必须将处理程序附加到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;
  }
}

Configure the handler as follows: 按如下方式配置处理程序:

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

... ...

collection.ItemPropertyChanged += OnItemPropertyChanged;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM