我现在在我的 XAML 代码中
<c:EditForLength Grid.Column="2" Value="{rx:ImmutableBinding Data.Peak}"/>
这变成了 ImmutableBinding 是一个标记扩展,它返回一个绑定到一个代理对象,所以你可以想象上面被重写为
<c:EditForLength Grid.Column="2" Value="{Binding Value, ValidatesOnNotifyDataErrors=True}"/>
直接绑定到代理对象,代理对象隐藏真实数据和验证错误。我的代理和不可变绑定对象的代码是
请注意,代码使用 ReactiveUI 调用和我自己的一些自定义代码,但使用代理构建自定义绑定以调解验证错误的一般模式应该很清楚。此外,代码可能会泄漏内存,我会尽快检查。
[MarkupExtensionReturnType(typeof(object))]
public class ImmutableBinding : MarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public ImmutableBinding(PropertyPath path) { Path = path; }
/// <summary>
/// Returns a custom binding that inserts a proxy object between
/// the view model and the binding that maps immutable persistent
/// writes to the DTO.
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
override public object ProvideValue( IServiceProvider provider )
{
var pvt = provider as IProvideValueTarget;
if ( pvt == null )
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if ( frameworkElement == null )
{
return this;
}
if ( frameworkElement.DataContext == null )
{
return "";
}
var proxy = new Proxy();
var binding = new Binding()
{
Source = proxy,
Path = new PropertyPath("Value"),
Mode = BindingMode.TwoWay,
ValidatesOnDataErrors = true
};
var path = Path.Path.Split('.');
var head = path.First();
var tail = path.Skip(1)
.ToList();
var data = frameworkElement.DataContext as ValidatingReactiveObject;
if (data == null)
return null;
data.ErrorsChanged += (s, e) =>
{
if ( data.Errors.ContainsKey(Path.Path) )
{
proxy.Errors["Value"] = data.Errors[Path.Path];
}
else
{
proxy.Errors.Clear();
}
proxy.RaiseValueErrorChanged();
};
var subscription = data
.WhenAnyDynamic(path, change => change.Value )
.Subscribe(value => proxy.Value = value);
proxy
.WhenAnyValue(p => p.Value)
.Skip(1)
.DistinctUntilChanged()
.Subscribe
(value =>
{
var old = data.GetType()
.GetProperty(head)
.GetValue(data) as Immutable;
if (old == null) throw new NullReferenceException("old");
var @new = old.Set(tail, value);
data.GetType()
.GetProperty(head)
.SetValue(data, @new);
});
binding.ValidatesOnNotifyDataErrors = true;
return binding.ProvideValue(provider);
}
}
和代理对象。注意 ValidatingReactiveObject 实现了 INotifyDataErrorInfo
public class Proxy : ValidatingReactiveObject<Proxy>
{
object _Value;
public object Value
{
get { return _Value; }
set { this.ValidateRaiseAndSetIfChanged(ref _Value, value); }
}
public Proxy() { Value = 0.0; }
public void RaiseValueErrorChanged()
{
RaiseErrorChanged("Value");
RaiseErrorChanged("Error");
OnPropertyChanged("Error");
}
}