简体   繁体   English

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

[英]Update XAML control property value from control's code but keep binding

I have written a control with a bindable property. 我用可绑定属性编写了一个控件。 This control also has a method to modify that property's value: 此控件还有一个方法来修改该属性的值:

public class MyControl : ContentView // WPF: inherited from UserControl
{
  // Xamarin:
  public static readonly BindableProperty MyValueProperty = ...
  // WPF:
  // public static readonly DependencyProperty MyValueProperty = ...

  public int MyValue
  {
     get { return (int) GetValue(MyValueProperty); }
     set { SetValue(MyValueProperty, value); }
  }

  public void Reset()
  {
    MyValue = 0;
  }
}

I am using that control in a normal XAML page and update MyValue via binding: 我在普通的XAML页面中使用该控件并通过绑定更新MyValue

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

The binding initially propagates changes from MyValueSource to MyValue . 绑定最初将更改从MyValueSource传播到MyValue But as soon as I call the Reset() method once, the binding is overwritten by the 0 and updates to MyValueSource are no longer pulled. 但是一旦我调用Reset()方法一次,绑定就被0覆盖,并且不再拉取对MyValueSource更新。

I suppose any direct assignment of MyValue is intended to replace a OneWay binding. 我想任何MyValue直接赋值都是为了取代OneWay绑定。 With a TwoWay binding, the change is just propagated back to MyValueSource and the binding remains functional. 使用TwoWay绑定,更改只会传播回MyValueSource ,绑定仍然有效。

If Reset() was in the view model, I could do this: 如果Reset()在视图模型中,我可以这样做:

public void Reset()
{
  // TheControl.MyValue = 0; // Bad practice, destroys the binding
  MyValueSource = 0; // Good practice, preserves the binding
}

I don't want to implement the reset logic (which is more complex than in this reduced example) in every VM though, so it's located in the view/control. 我不想在每个VM中实现重置逻辑(这比在这个简化示例中更复杂),因此它位于视图/控件中。

So I wonder - can you assign a bindable property's value from the control's code behind and still preserve a possible OneWay binding? 所以我想知道 - 你可以控件的代码中分配一个可绑定属性的值并仍然保留一个可能的OneWay绑定吗? I know this means the VM does not get the changed value; 我知道这意味着VM没有获得更改的值; binding OneWay is likely not correct if the control updates the property as well; 绑定OneWay可能不正确,如果控件也更新属性; you should rather use a TwoWay binding then. 你应该使用TwoWay绑定。

But if someone says OneWay in XAML, I'd rather have it behave that way down to the wire than implement some " OneWay until you call Reset() " behavior. 但是,如果有人在XAML中使用OneWay ,我宁愿让它表现得像是在执行一些“ OneWay直到你调用Reset() ”行为。

Side note: I am working in Xamarin, but I guess the behavior is the same for WPF. 旁注:我在Xamarin工作,但我猜WPF的行为是一样的。

Taken and fleshed out from @Clemens' comment : 从@Clemens的评论中摘取并充实:

WPF WPF

You can use the SetCurrentValue method on an DependencyObject (ie the control) to change the current effective value of a DependencyProperty . 您可以在DependencyObject (即控件)上使用SetCurrentValue方法来更改DependencyProperty的当前有效值。 Unlike SetValue , with SetCurrentValue any triggers, data bindings and styles to that property remain intact. SetValue不同,使用SetCurrentValue ,该属性的任何触发器,数据绑定和样式都保持不变。

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

Remember that if you defined a OneWay binding, the view model will not be notified about the changed value, and that any change to the VM's MyValueSource property will override the control's value again (if the property is implemented correctly). 请记住,如果您定义了OneWay绑定,则不会通知视图模型更改的值,并且对VM的MyValueSource属性的任何更改都将再次覆盖控件的值(如果属性正确实现)。


Xamarin Xamarin

There is currently no proper way to assign a BindableProperty 's value without replacing a OneWay binding attached to it. 目前没有正确的方法来分配BindableProperty的值而不替换附加到它的OneWay绑定。 BindableObject (the control's base class) does not have any method comparable to WPF's SetCurrentValue and SetValue will allways replace the binding. BindableObject (控件的基类)没有任何与WPF的SetCurrentValue相当的方法, SetValue将总是替换绑定。

However, if you change the binding to BindingMode.TwoWay , the internal value change is propagated back to the view model. 但是,如果将绑定更改为BindingMode.TwoWay ,则内部值更改将传播回视图模型。 You should probably do this anyway to keep the control and the VM synchronized. 无论如何,您应该这样做以保持控件和VM同步。

public void Reset()
{
  // Replaces any OneWay bindings
  // Updates MyValueSource for TwoWay bindings
  this.SetValue(MyValueProperty, 0);
}

Here is the Hacky WPF equivalent for Xamarin, for OneWay binding: 这是针对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);
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM