[英]Custom binding to a property in XAML / WPF
例如,如果我有一个像这样的视图模型类
class ViewModel {
Data Data { get; set;}
}
和
class Data : IClonable {
public int Value0 {get; private set;}
public int Value1 {get; private set;}
Data SetValue0(int value){
var r = (Data) this.Clone();
r.Value0 = value;
return r;
}
Data SetValue1(int value){
var r = (Data) this.Clone();
r.Value1 = value;
return r;
}
}
在我的XAML中,以ViewModel的实例作为DataContext,我想以两种方式像这样绑定文本框
<TextBox Text="{Binding Data.Value0}"/>
<TextBox Text="{Binding Data.Value1}"/>
现在显然这是行不通的。 我需要对绑定进行解释的是,要设置子属性,我必须调用方法Set${propName}
,然后从路径的根目录替换整个属性。
请注意,我对不可变对象模式的实际实现要比上面复杂得多,但是可以为我的更复杂的设置推断出上述模式的解决方案。 有什么我可以提供给绑定的东西来使其执行我想要的吗?
作为信息,我实际的不可变对象模式允许类似
var newValue = oldValue.Set(p=>p.Prop1.Prop2.Prop3.Prop4, "xxx");
我现在在我的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");
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.