使用MVVM更新依赖属性

ali*_*ray 9 c# wpf mvvm

我的一些属性viewmodel:

public ObservableCollection<Task> Tasks { get; set; }

public int Count
{
    get { return Tasks.Count; }
}

public int Completed
{
    get { return Tasks.Count(t => t.IsComplete); }
}
Run Code Online (Sandbox Code Playgroud)

Tasks更改时更新这些属性的最佳方法是什么?

我目前的方法:

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

有没有更优雅的方式来做到这一点?

Jay*_*Jay 9

关于Count,你根本不需要这样做.只需绑定Tasks.Count,您的绑定将通过以下方式通知您的更改ObservableCollection.

Completed是一个不同的故事,因为这是在...之外ObservableCollection.不过,从抽象/界面的层面来看,你真的想Completed成为该Tasks集合的一个属性.

为此,我认为更好的方法是为您的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");
}
Run Code Online (Sandbox Code Playgroud)

这为您提供了所有的好处ObservableCollection,并有效地使Completed财产成为其中的一部分.我们仍然没有捕获完成项目数量确实发生变化的情况,但我们在一定程度上减少了冗余通知的数量.

现在viewmodel只有属性:

public TasksViewModel Tasks { get; set; }
Run Code Online (Sandbox Code Playgroud)

...你可以绑定到Tasks,Tasks.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您甚至可以将房产类型保留为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));
    }
}
Run Code Online (Sandbox Code Playgroud)