从控件的代码更新XAML控件属性值但保持绑定

时间:2017-01-06 11:55:13

标签: c# xaml data-binding

我已经用可绑定属性编写了一个控件。此控件还有一个方法来修改该属性的值:

<local:MyControl x:Name="TheControl"
                 MyValue="{Binding MyValueSource, Mode=OneWay}" />

我在普通的XAML页面中使用该控件,并通过绑定更新MyValueSource

MyValue

绑定最初会将更改从Reset()传播到0。但是,只要我调用MyValueSource方法一次,MyValue就会覆盖绑定,并且不再对OneWay进行更新。

我认为TwoWay的任何直接分配都是为了取代MyValueSource绑定。使用Reset()绑定,更改只会传播回public void Reset() { // TheControl.MyValue = 0; // Bad practice, destroys the binding MyValueSource = 0; // Good practice, preserves the binding } ,绑定仍然有效。

如果OneWay在视图模型中,我可以这样做:

OneWay

我不希望在每个VM中实现重置逻辑(比这个简化示例中的更复杂),因此它位于视图/控件中。

所以我想 - 可以控件的代码中分配一个可绑定属性的值,并且仍然保留可能的TwoWay绑定?我知道这意味着VM没有获得更改的值;如果控件也更新属性,则绑定OneWay可能不正确;你应该使用OneWay绑定。

但是如果有人在XAML中说Reset(),我宁愿让它表现得如同行动,而不是实施一些“<Router onUpdate={() => window.scrollTo(0, 0)} history= {browserHistory}> ... </Router> ,直到你调用function scrollToTop() { window.scrollTo(0, 0) if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; } } <Router onUpdate= {scrollToTop} history={browserHistory}> .... </Router> ”行为。

旁注:我在Xamarin工作,但我猜WPF的行为是一样的。

2 个答案:

答案 0 :(得分:4)

采取并充实from @Clemens' comment

WPF

您可以在SetCurrentValue(即控件)上使用DependencyObject方法更改DependencyProperty的当前有效值。与SetValue不同,SetCurrentValue任何触发器,数据绑定和该属性的样式都保持不变。

public void Reset()
{
  // this.SetValue(MyValueProperty, 0); // Replaces the binding
  this.SetCurrentValue(MyValueProperty, 0); // Keeps the binding
}

请记住,如果您定义了OneWay绑定,则视图模型将通知有关更改的值,以及对VM {{1}的任何更改}属性将再次覆盖控件的值(如果属性正确实现)。

Xamarin

目前没有正确的方法来分配BindableProperty的值而不替换附加到它的MyValueSource绑定。 BindableObject(控件的基类)没有任何与WPF相似的方法OneWaySetValue将总是取代绑定。

但是,如果将绑定更改为SetCurrentValue,则内部值更改将传播回视图模型。无论如何,您应该这样做以保持控件和VM同步。

BindingMode.TwoWay

答案 1 :(得分:0)

这是针对Xamarin的Hacky WPF等价物,用于OneWay绑定:

public static class BindingObjectExtensions
{
public static Binding GetBinding(this BindableObject self, BindableProperty property)
        {
            if (self == null)
            {
                throw new ArgumentNullException(nameof(self));
            }
            if (property == null)
            {
                throw new ArgumentNullException(nameof(property));
            }
            var methodInfo = typeof(BindableObject).GetTypeInfo().GetDeclaredMethod("GetContext");
            var context = methodInfo?.Invoke(self, new object[] { property });

            var propertyInfo = context?.GetType().GetTypeInfo().GetDeclaredField("Binding");
            return propertyInfo?.GetValue(context) as Binding;
        }

public static void SetCurrentValue(this BindableObject self, BindableProperty property, object value)
        {
            if (self == null)
            {
                throw new ArgumentNullException(nameof(self));
            }
            if (property == null)
            {
                throw new ArgumentNullException(nameof(property));
            }
            var backupBinding = self.GetBinding(property);//backup binding
            var backupConverter = backupBinding.Converter;//backup orig. converter
            self.SetValue(property,value);//removes the binding.
            backupBinding.Converter = new DefaultValueConverter {DefaultValue = value};//change the converter
            self.SetBinding(property, backupBinding);//target should be updated to the default value
            var converterField = backupBinding.GetType().GetTypeInfo().GetDeclaredField("_converter");
            converterField.SetValue(backupBinding, backupConverter);//restore the converter
        }
}

//the default value converter class

[ContentProperty(nameof(DefaultValue))]
    public class DefaultValueConverter : BindableObject, IValueConverter, IMarkupExtension<DefaultValueConverter>
    {
        public object DefaultValue
        {
            get => GetValue(DefaultValueProperty);
            set => SetValue(DefaultValueProperty, value);
        }

        public static readonly BindableProperty DefaultValueProperty =
            BindableProperty.Create(nameof(DefaultValue), typeof(object), typeof(DefaultValueConverter));

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DefaultValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DefaultValue;
        }


        public DefaultValueConverter ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }

        object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
        {
            return ((IMarkupExtension<DefaultValueConverter>) this).ProvideValue(serviceProvider);
        }
    }