[英]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.