ObservableCollection - PullRequest
       0

ObservableCollection

2 голосов
/ 17 сентября 2010

У меня есть диалог WPF, связанный со списком типа ObservableCollection<MyEntity>. В этом диалоговом окне я хочу, чтобы кнопка «ОК» была включена только в случае внесения изменений в список ObservableCollection<MyEntity>, который включает добавление / удаление элементов из списка и изменение отдельных элементов в списке.

Для добавления / удаления элементов из списка это легко - я реализовал обработчик для события CollectionChanged.

То, что я не знаю, как это сделать, - это когда отдельный предмет изменяется. Скажем, MyEntity.Name = "New Value", какой интерфейс должен реализовать класс MyEntity, чтобы сделать его "наблюдаемым"?

Ответы [ 4 ]

9 голосов
/ 17 сентября 2010

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 (через привязку установив его напрямую).

0 голосов
/ 18 мая 2011

Другим решением может быть настраиваемая наблюдаемая коллекция, для реализации которой требуются элементы 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;
0 голосов
/ 17 сентября 2010

Вы должны реализовать 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; 
0 голосов
/ 17 сентября 2010

Мне нравится ответ, предоставленный slugster, вот альтернативный вариант ответа slugster.

Если вы связываете свою кнопку OK с помощью DelegateCommnd, вы можете добавить обработчики событий для 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));
     }
  }
}
...