我已经用可绑定属性编写了一个控件。此控件还有一个方法来修改该属性的值:
<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的行为是一样的。
答案 0 :(得分:4)
采取并充实from @Clemens' comment:
您可以在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}的任何更改}属性将再次覆盖控件的值(如果属性正确实现)。
目前没有正确的方法来分配BindableProperty
的值而不替换附加到它的MyValueSource
绑定。 BindableObject
(控件的基类)没有任何与WPF相似的方法OneWay
和SetValue
将总是取代绑定。
但是,如果将绑定更改为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);
}
}