使用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");
};
}
有更优雅的方法吗?
对于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");
}
这为您提供了ObservableCollection
的所有好处,并有效地使Completed
属性成为其中的一部分。我们仍然没有只捕捉到已完成项目数量真正发生变化的情况,但我们已经在一定程度上减少了冗余通知的数量。
现在视图模型只有属性:
public TasksViewModel Tasks { get; set; }
…并且您可以轻松地绑定到Tasks
、Tasks.Count
和Tasks.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;
是更好的选择。)
并没有使关于Count
和Completed
的通知更优雅,但它修复了一个错误。
许多MVVM框架从lambda中获取属性名称,因此您可以编写OnPropertyChanged(() => Count)
,而不是OnPropertyChanged("Count")
,这样它就可以在重构工具的帮助下进行重命名。不过,我不认为重命名经常发生,但它确实避免了一些字符串文字。