我在我的MVVM库中使用以下类来允许属性更改向相关属性级联。如果您认为它对您有用,请随意使用:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace AgentOctal.WpfLib
{
public class PropertyChangeCascade<T> where T : ObservableObject
{
public PropertyChangeCascade(ObservableObject target)
{
Target = target;
Target.PropertyChanged += PropertyChangedHandler;
_cascadeInfo = new Dictionary<string, List<string>>();
}
public ObservableObject Target { get; }
public bool PreventLoops { get; set; } = false;
private Dictionary<string, List<string>> _cascadeInfo;
public PropertyChangeCascade<T> AddCascade(string sourceProperty,
List<string> targetProperties)
{
List<string> cascadeList = null;
if (!_cascadeInfo.TryGetValue(sourceProperty, out cascadeList))
{
cascadeList = new List<string>();
_cascadeInfo.Add(sourceProperty, cascadeList);
}
cascadeList.AddRange(targetProperties);
return this;
}
public PropertyChangeCascade<T> AddCascade(Expression<Func<T, object>> sourceProperty,
Expression<Func<T, object>> targetProperties)
{
string sourceName = null;
var lambda = (LambdaExpression)sourceProperty;
if (lambda.Body is MemberExpression expressionS)
{
sourceName = expressionS.Member.Name;
}
else if (lambda.Body is UnaryExpression unaryExpression)
{
sourceName = ((MemberExpression)unaryExpression.Operand).Member.Name;
}
else
{
throw new ArgumentException("sourceProperty must be a single property", nameof(sourceProperty));
}
var targetNames = new List<string>();
lambda = (LambdaExpression)targetProperties;
if (lambda.Body is MemberExpression expression)
{
targetNames.Add(expression.Member.Name);
}
else if (lambda.Body is UnaryExpression unaryExpression)
{
targetNames.Add(((MemberExpression)unaryExpression.Operand).Member.Name);
}
else if (lambda.Body.NodeType == ExpressionType.New)
{
var newExp = (NewExpression)lambda.Body;
foreach (var exp in newExp.Arguments.Select(argument => argument as MemberExpression))
{
if (exp != null)
{
var mExp = exp;
targetNames.Add(mExp.Member.Name);
}
else
{
throw new ArgumentException("Syntax Error: targetProperties has to be an expression " +
"that returns a new object containing a list of " +
"properties, e.g.: s => new { s.Property1, s.Property2 }");
}
}
}
else
{
throw new ArgumentException("Syntax Error: targetProperties has to be an expression " +
"that returns a new object containing a list of " +
"properties, e.g.: s => new { s.Property1, s.Property2 }");
}
return AddCascade(sourceName, targetNames);
}
public void Detach()
{
Target.PropertyChanged -= PropertyChangedHandler;
}
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
List<string> cascadeList = null;
if (_cascadeInfo.TryGetValue(e.PropertyName, out cascadeList))
{
if (PreventLoops)
{
var cascaded = new HashSet<string>();
cascadeList.ForEach(cascadeTo =>
{
if (!cascaded.Contains(cascadeTo))
{
cascaded.Add(cascadeTo);
Target.RaisePropertyChanged(cascadeTo);
}
});
}
else
{
cascadeList.ForEach(cascadeTo =>
{
Target.RaisePropertyChanged(cascadeTo);
});
}
}
}
}
}
ObservableObject
是实现 INotifyPropertyChanged
接口的基类。您应该可以很容易地替换为自己的基类。
您可以像这样使用它:
class CascadingPropertyVM : ViewModel
{
public CascadingPropertyVM()
{
new PropertyChangeCascade<CascadingPropertyVM>(this)
.AddCascade(s => s.Name,
t => new { t.DoubleName, t.TripleName });
}
private string _name;
public string Name
{
get => _name;
set => SetValue(ref _name, value);
}
public string DoubleName => $"{Name} {Name}";
public string TripleName => $"{Name} {Name} {Name}";
}
构造函数中的代码将
Name
属性的更改与
DoubleName
和
TripleName
属性的级联变化相连接。为了性能方面的考虑,它默认不会检查级联中是否存在循环,因此需要您避免创建这种情况。您可以选择在级联上设置
PreventLoops
为
true
,这样它就会确保每个属性只触发一次
PropertyChanged
。