我之前写过这个程序,一直运行得很好。
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class CalculatedProperty : Attribute
{
private string[] _props;
public CalculatedProperty(params string[] props)
{
this._props = props;
}
public string[] Properties
{
get
{
return _props;
}
}
}
视图模型基类
public class ObservableObject : INotifyPropertyChanged
{
private static Dictionary<string, Dictionary<string, string[]>> calculatedPropertiesOfTypes = new Dictionary<string, Dictionary<string, string[]>>();
private readonly bool hasComputedProperties;
public ObservableObject()
{
Type t = GetType();
if (!calculatedPropertiesOfTypes.ContainsKey(t.FullName))
{
var props = t.GetProperties();
foreach (var pInfo in props)
{
var attr = pInfo.GetCustomAttribute<CalculatedProperty>(false);
if (attr == null)
continue;
if (!calculatedPropertiesOfTypes.ContainsKey(t.FullName))
{
calculatedPropertiesOfTypes[t.FullName] = new Dictionary<string, string[]>();
}
calculatedPropertiesOfTypes[t.FullName][pInfo.Name] = attr.Properties;
}
}
if (calculatedPropertiesOfTypes.ContainsKey(t.FullName))
hasComputedProperties = true;
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
if (this.hasComputedProperties)
{
var computedPropNames =
calculatedPropertiesOfTypes[this.GetType().FullName]
.Where(kvp => kvp.Value.Contains(propertyName))
.Select(kvp => kvp.Key);
if (computedPropNames != null)
if (!computedPropNames.Any())
return;
foreach (var computedPropName in computedPropNames)
{
if (computedPropName == propertyName)
throw new InvalidOperationException("A property can't depend on itself");
OnPropertyChanged(computedPropName);
}
}
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
例子:
public class ViewModel : ObservableObject
{
private int _x;
public int X
{
get { return _x; }
set { SetField(ref _x, value); }
}
private int _y;
public int Y
{
get { return _y; }
set { SetField(ref _y, value); }
}
[CalculatedProperty("X", "Y")]
public int Z
{
get { return X * Y; }
}
[CalculatedProperty("Z")]
public int M
{
get { return Y * Z; }
}
}
注意:
- 它仅对每种类型使用一次反射
- `SetField` 设置字段并在有新值时引发属性更改
- 只要在 setter 内部调用它,就不需要向 `SetField` 传递属性名称,因为自 C# 5.0 以来 `[CallerMemberName]` 会自动帮您完成。
- 如果在 setter 之外调用 `SetField`,则必须传递属性名称。
- 根据我的最新更新,您可以通过直接设置字段,然后调用 `OnPropertyChanged("PropertyName")` 来避免使用 `SetField`,它将为所有依赖它的属性引发 PropertyChanged 事件。
- 在 C# 6 中,您可以使用 nameof 运算符获取属性名称,例如 `nameof(Property)`
- 如果存在计算属性,则 `OnPropertyChanged` 将递归调用自身
测试用的 XAML
<StackPanel>
<TextBox Text="{Binding X,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBox Text="{Binding Y,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="{Binding Z,Mode=OneWay}"></TextBlock>
<TextBlock Text="{Binding M,Mode=OneWay}"></TextBlock>
</StackPanel>
RaisePropertyChanged(nameof(EnableConsignor))
,这是重构安全的。如果您无法使用C# 6.0并且没有性能关键代码,则仍然可以使用自定义实现的RaisePropertyChanged,该实现接受表达式并将其用作获取属性名称的字符串。 - Tseng