使用MVVM更新依赖属性

本文关键字:属性 依赖 更新 MVVM 使用 | 更新日期: 2023-09-27 17:58:49

我的viewmodel:上的一些属性

public ObservableCollection<Task> Tasks { get; set; }
public int Count
{
    get { return Tasks.Count; }
}
public int Completed
{
    get { return Tasks.Count(t => t.IsComplete); }
}

Tasks发生更改时,更新这些属性的最佳方式是什么?

我当前的方法

public TaskViewModel()
{
    Tasks = new ObservableCollection<Task>(repository.LoadTasks());
    Tasks.CollectionChanged += (s, e) => 
        {
            OnPropertyChanged("Count");
            OnPropertyChanged("Completed");
        };
}

有更优雅的方法吗?

使用MVVM更新依赖属性

对于Count,您根本不需要这样做。只需绑定到Tasks.CountObservableCollection就会通知您的绑定更改。

Completed是另一回事,因为这是在ObservableCollection之外。不过,从抽象/接口的级别来看,您确实希望CompletedTasks集合的一个属性。

为此,我认为更好的方法是为Tasks属性创建"子"视图模型:

public class TasksViewModel : ObservableCollection<Task>
{
    public int Completed
    {
        get { return this.Count(t => t.IsComplete); }
    }
    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if(e.PropertyName == "Count") NotifyCompletedChanged();
    }
    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        NotifyCompletedChanged();
    }
    void NotifyCompletedChanged()
    {
        OnPropertyChanged(_completedChangedArgs);
    }
    readonly PropertyChangedEventArgs _completedChangedArgs = new PropertyChangedEventArgs("Completed");
}

这为您提供了ObservableCollection的所有好处,并有效地使Completed属性成为其中的一部分。我们仍然没有只捕捉到已完成项目数量真正发生变化的情况,但我们已经在一定程度上减少了冗余通知的数量。

现在视图模型只有属性:

public TasksViewModel Tasks { get; set; }

…并且您可以轻松地绑定到TasksTasks.CountTasks.Completed


作为替代方案,如果您更愿意在"主"视图模型上创建这些其他属性,则可以采用子类ObservableCollection<T>的概念来创建一个具有某种方法的属性,在该方法中可以传递Action<string>委托,这将表示在主视图模型上引发属性更改通知,以及一些属性名称列表。然后,此集合可以有效地在视图模型上引发属性更改通知:

public class ObservableCollectionWithSubscribers<T> : ObservableCollection<T>
{
    Action<string> _notificationAction = s => { }; // do nothing, by default
    readonly IList<string> _subscribedProperties = new List<string>();
    public void SubscribeToChanges(Action<string> notificationAction, params string[] properties)
    {
        _notificationAction = notificationAction;
        foreach (var property in properties)
            _subscribedProperties.Add(property);
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        NotifySubscribers();
    }
    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        NotifySubscribers();
    }
    void NotifySubscribers()
    {
        foreach (var property in _subscribedProperties)
            _notificationAction(property);
    }
}

您甚至可以将属性类型保留为ObservableCollection<Task>

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        var tasks = new ObservableCollectionWithSubscribers<Task>();
        tasks.SubscribeToChanges(Notify, "Completed");
        Tasks = tasks;
    }
    public ObservableCollection<Task> Tasks { get; private set; }
    public int Completed
    {
        get { return Tasks.Count(t => t.IsComplete); }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    void Notify(string property)
    {
        var handler = PropertyChanged;
        if(handler != null) handler(this, new PropertyChangedEventArgs(property));
    }
}

在我看来相当优雅。我真的不知道你该如何让它更简洁

(写这样的答案真奇怪。如果有人真的想出了更优雅的东西,我可能会删除它。)

好吧,我注意到一件事,与最初的问题无关:您的Tasks属性有一个公共setter。将其设为private set;,或者您需要使用一个backing字段来实现set,这样您就可以删除上一个实例上的委托,替换并连接新实例,并使用"Tasks"、"Count"answers"Completed"来执行OnPropertyChanged(看看Tasks是如何在构造函数中设置的,我猜private set;是更好的选择。)

并没有使关于CountCompleted的通知更优雅,但它修复了一个错误。

许多MVVM框架从lambda中获取属性名称,因此您可以编写OnPropertyChanged(() => Count),而不是OnPropertyChanged("Count"),这样它就可以在重构工具的帮助下进行重命名。不过,我不认为重命名经常发生,但它确实避免了一些字符串文字。