可观察集合

发布于 2024-09-19 18:23:27 字数 357 浏览 4 评论 0原文

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

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

我不知道当修改单个项目时该怎么做。比如说,MyEntity.Name="New Value",MyEntity 类需要实现什么接口才能使其“可观察”?

I have a WPF dialog that is bound to a list of ObservableCollection<MyEntity> type. 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.

For adding/removing items from the list, it is easy - I implemented a handler for the CollectionChanged event.

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'?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

风筝有风,海豚有海 2024-09-26 18:23:27

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中的任何对象是否已设置为dirty。此检查可以使用简单的 LINQ 语句来完成:myCollection.Any(x => x.IsDirty == true)

  • 此方法更加笨重且有异味......有一个外部对象侦听更改(通过订阅每个对象上的 PropertyChanged 事件),然后该外部侦听器可以启用“确定”按钮(通过数据绑定或直接设置)。

MyEntity needs to implement INotifyPropertyChanged, then when a property change occurs you fire the PropertyChanged event. 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. 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. This check can be done with a simple LINQ statement: 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).

沧笙踏歌 2024-09-26 18:23:27

我喜欢 slugster 提供的答案,这是基于 slugster 答案的替代方案。

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

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

I like the answer provided by slugster, here is an alternative building on slugster's answer.

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.

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):
Only the Name property fires an event in this example...

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));
     }
  }
}
青春有你 2024-09-26 18:23:27

您应该实现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; 

You should implement 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

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

public int MyProperty
{
     get { return _myProperty; }
     set
        {
           if (value != _myProperty)
           {
               _subVersion = value;
               OnPropertyChanged(() => MyProperty);
            }
        }
} private int _myProperty; 
我不在是我 2024-09-26 18:23:27

另一种解决方案可能是自定义可观察集合,它需要项目实现 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;

Another solution could be a custom observable collection that requires items to implement 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.

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文