【问题标题】:Custom binding to a property in XAML / WPF自定义绑定到 XAML/WPF 中的属性
【发布时间】:2014-04-02 14:27:45
【问题描述】:

例如,如果我有一个类似的视图模型类

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");

【问题讨论】:

  • 就在我的头上,您可以使用带有两条腿的 Multibinding + Converter 您的对象(您的 VM 的一个属性)及其属性名称,可能可以是文本) - 虽然听起来仍然很扭曲。通常 WPF 对链接并不太友好。老实说,我不喜欢它 - 大多数时候闻起来像 VBA。您不能将您的虚拟机定制为具有单个对象(因此绑定不会混淆)并使用其属性进行操作以响应其属性更改...
  • 我有一个我认为的解决方案。一个自定义绑定 MarkupExtension,它闻起来就像普通绑定,但在底层具有智能,可以按照我想要的方式重定向写入。您可以像 &lt;TextBox Text="{c:ImmutableBinding Path=Data.Value1}/&gt; 一样使用它

标签: wpf xaml binding immutability


【解决方案1】:

我现在在我的 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");
    }
}

【讨论】:

    猜你喜欢
    • 2014-12-18
    • 2011-12-20
    • 2023-01-16
    • 2018-04-09
    • 2011-12-14
    • 1970-01-01
    • 1970-01-01
    • 2022-12-02
    • 1970-01-01
    相关资源
    最近更新 更多