强制强制传播强制价值 [英] Force Propagation of Coerced Value

查看:117
本文介绍了强制强制传播强制价值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tl; dr:强制值不会跨数据绑定传播。当代码隐藏不知道绑定的另一面时,如何强制更新数据绑定?






我在WPF依赖关系属性上使用了一个 CoerceValueCallback ,而我坚持认为强制值不会传播到绑定的问题。 / p>

Window1.xaml.cs

 使用系统; 
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Data;
使用System.Windows.Media;

命名空间CoerceValueTest
{
public class SomeControl:UserControl
{
public SomeControl()
{
StackPanel sp = new StackPanel();

按钮bUp = new Button();
bUp.Content =+;
bUp.Click + = delegate(object sender,RoutedEventArgs e){
Value + = 2;
};

按钮bDown = new Button();
bDown.Content = - ;
bDown.Click + = delegate(object sender,RoutedEventArgs e){
Value - = 2;
};

TextBlock tbValue = new TextBlock();
tbValue.SetBinding(TextBlock.TextProperty,
new Binding(Value){
Source = this
});

sp.Children.Add(bUp);
sp.Children.Add(tbValue);
sp.Children.Add(bDown);

this.Content = sp;
}

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(Value,
typeof(int),
typeof(SomeControl),
new PropertyMetadata(0,ProcessValueChanged,CoerceValue));

私有静态对象CoerceValue(DependencyObject d,object baseValue)
{
if((int)baseValue%2 == 0){
return baseValue;
} else {
return DependencyProperty.UnsetValue;
}
}

private static void ProcessValueChanged(object source,DependencyPropertyChangedEventArgs e)
{
((SomeControl)source).ProcessValueChanged(e);
}

private void ProcessValueChanged(DependencyPropertyChangedEventArgs e)
{
OnValueChanged(EventArgs.Empty);
}

protected virtual void OnValueChanged(EventArgs e)
{
if(e == null){
throw new ArgumentNullException(e) ;
}

if(ValueChanged!= null){
ValueChanged(this,e);
}
}

公共事件EventHandler ValueChanged;

public int Value {
get {
return(int)GetValue(ValueProperty);
}
set {
SetValue(ValueProperty,value);
}
}
}

public class SomeBiggerControl:UserControl
{
public SomeBiggerControl()
{
Border parent = new Border();
parent.BorderThickness = new Thickness(2);
parent.Margin = new Thickness(2);
parent.Padding = new Thickness(3);
parent.BorderBrush = Brushes.DarkRed;

SomeControl ctl = new SomeControl();
ctl.SetBinding(SomeControl.ValueProperty,
new Binding(Value){
Source = this,
Mode = BindingMode.TwoWay
});
parent.Child = ctl;

this.Content = parent;


public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(Value,
typeof(int),
typeof(SomeBiggerControl),
new PropertyMetadata(0));

public int Value {
get {
return(int)GetValue(ValueProperty);
}
set {
SetValue(ValueProperty,value);
}
}
}

public partial class Window1:Window
{
public Window1()
{
InitializeComponent();
}
}
}

Window1.xaml

 < Window x:Class =CoerceValueTest.Window1
xmlns =http: /schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x =http://schemas.microsoft.com/winfx/2006/xaml
标题=CoerceValueTest高度=300Width =300
xmlns:local =clr-namespace:CoerceValueTest
>
< StackPanel>
< local:SomeBiggerControl x:Name =sc/>
< TextBox Text ={Binding Value,ElementName = sc,Mode = TwoWay}Name =tb/>
< Button Content =/>
< / StackPanel>
< / Window>

ie。两个用户控件,一个嵌套在另一个中,另一个在外部窗口中。内部用户控件具有绑定到外部控件的依赖属性的依赖属性。在窗口中, TextBox.Text 属性绑定到外部控件的属性。



内部控件有一个 CoerceValueCallback 注册到其属性,其效果这个属性只能分配偶数号。



请注意,此代码被简化为示范目的。真正的版本不会在构造函数中初始化任何东西;这两个控件实际上具有控制模板,可以在这里完成相应构造函数中的所有操作。也就是说,在真正的代码中,外部控件不知道内部控件。



在文本框中写入偶数,并更改焦点(例如通过将文本框下方的虚拟按钮对齐),属性的属性将被正确更新。然而,当在文本框中写入一个奇数时,内部控件的属性不会更改,而属性的外部控件,以及 TextBox.Text 属性,显示奇数。



我的问题是:如何在文本框中强制更新(最好还是在外部控件的属性中)



我发现

此外,这个blogpost 建议在 TextBox.Text中的绑定上调用 UpdateTarget CoerceValueCallback 中,但首先,如上所述,我的内部控制不可能有任何关于文本框的知识,第二,我可能必须在内部控件的属性的绑定上调用 UpdateSource 。但是,如果在 CoerceValue 方法中,强制值尚未设置(因此更新绑定为时尚早),而在值被重置为 CoerceValue 的情况下,属性值将保持原样,因此更改回调的属性将不会被调用(也可以在< a href =http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/cc2f0fe3-a7b3-4f17-8915-a72a41fe4729/ =nofollow>这个讨论) 。



我想到的一种可能的解决方法是将 SomeControl 中的依赖属性替换为常规属性, code> INotifyPropertyChanged 实现(所以我可以手动触发 PropertyChanged 事件,即使该值已被胁迫)。但是,这意味着我不能再声明对该属性的约束,所以它不是一个非常有用的解决方案。

解决方案

我一直在寻找一个这个相当讨厌的bug的答案。
这样做的一种方式是,而不需要强制绑定上的UpdateTarget:




  • 删除你的 CoerceValue 回调。

  • 将CoerceValue 回调的逻辑转移到您的ProcessValueChanged回调中。

  • 将您的胁迫值分配给您的价值属性(适用时(数字为奇数))

  • 您最终将使用 ProcessValueChanged 回调被击中两次,但是您的胁迫值将最终被有效地推送到您的绑定。



根据您的代码,你的依赖属性声明将成为:

  public static readonly DependencyProperty ValueProperty = 
DependencyProperty.Register(Value ,
typeof(int),
typeof(SomeControl),
新的PropertyMetadata(0,ProcessValueChanged,null));

然后,您的 ProcessValueChanged 将成为:

  private static void ProcessValueChanged(object source,DependencyPropertyChangedEventArgs e)
{
int baseValue =(int)e.NewValue;
SomeControl someControl = source为SomeControl;
if(baseValue%2!= 0)
{
someControl.Value = DependencyProperty.UnsetValue;
}
else
{
someControl.ProcessValueChanged(e);
}
}

我略微修改了您的逻辑,以防止提高事件当价值需要强制时。如之前提到的那样,分配给 someControl.Value ,强制的值将导致您的连接被调用两次的 ProcessValueChanged 。放置else语句只会提高有效值的事件一次。



希望这有帮助!


tl;dr: Coerced values are not propagated across data bindings. How can I force the update across the data binding when code-behind doesn't know the other side of the binding?


I'm using a CoerceValueCallback on a WPF dependency property and I'm stuck at the issue that coerced values don't get propagated through to bindings.

Window1.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace CoerceValueTest
{
    public class SomeControl : UserControl
    {
        public SomeControl()
        {
            StackPanel sp = new StackPanel();

            Button bUp = new Button();
            bUp.Content = "+";
            bUp.Click += delegate(object sender, RoutedEventArgs e) {
                Value += 2;
            };

            Button bDown = new Button();
            bDown.Content = "-";
            bDown.Click += delegate(object sender, RoutedEventArgs e) {
                Value -= 2;
            };

            TextBlock tbValue = new TextBlock();
            tbValue.SetBinding(TextBlock.TextProperty,
                               new Binding("Value") {
                                Source = this
                               });

            sp.Children.Add(bUp);
            sp.Children.Add(tbValue);
            sp.Children.Add(bDown);

            this.Content = sp;
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
                                                                                              typeof(int),
                                                                                              typeof(SomeControl),
                                                                                              new PropertyMetadata(0, ProcessValueChanged, CoerceValue));

        private static object CoerceValue(DependencyObject d, object baseValue)
        {
            if ((int)baseValue % 2 == 0) {
                return baseValue;
            } else {
                return DependencyProperty.UnsetValue;
            }
        }

        private static void ProcessValueChanged(object source, DependencyPropertyChangedEventArgs e)
        {
            ((SomeControl)source).ProcessValueChanged(e);
        }

        private void ProcessValueChanged(DependencyPropertyChangedEventArgs e)
        {
            OnValueChanged(EventArgs.Empty);
        }

        protected virtual void OnValueChanged(EventArgs e)
        {
            if (e == null) {
                throw new ArgumentNullException("e");
            }

            if (ValueChanged != null) {
                ValueChanged(this, e);
            }
        }

        public event EventHandler ValueChanged;

        public int Value {
            get {
                return (int)GetValue(ValueProperty);
            }
            set {
                SetValue(ValueProperty, value);
            }
        }
    }

    public class SomeBiggerControl : UserControl
    {
        public SomeBiggerControl()
        {
            Border parent = new Border();
            parent.BorderThickness = new Thickness(2);
            parent.Margin = new Thickness(2);
            parent.Padding = new Thickness(3);
            parent.BorderBrush = Brushes.DarkRed;

            SomeControl ctl = new SomeControl();
            ctl.SetBinding(SomeControl.ValueProperty,
                           new Binding("Value") {
                            Source = this,
                            Mode = BindingMode.TwoWay
                           });
            parent.Child = ctl;

            this.Content = parent;
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
                                                                                              typeof(int),
                                                                                              typeof(SomeBiggerControl),
                                                                                              new PropertyMetadata(0));

        public int Value {
            get {
                return (int)GetValue(ValueProperty);
            }
            set {
                SetValue(ValueProperty, value);
            }
        }
    }

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}

Window1.xaml

<Window x:Class="CoerceValueTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="CoerceValueTest" Height="300" Width="300"
    xmlns:local="clr-namespace:CoerceValueTest"
    >
    <StackPanel>
        <local:SomeBiggerControl x:Name="sc"/>
        <TextBox Text="{Binding Value, ElementName=sc, Mode=TwoWay}" Name="tb"/>
        <Button Content=" "/>
    </StackPanel>
</Window>

i.e. two user controls, one nested inside the other, and the outer one of those in a window. The inner user control has a Value dependency property that is bound to a Value dependency property of the outer control. In the window, a TextBox.Text property is bound to the Value property of the outer control.

The inner control has a CoerceValueCallback registered with its Value property whose effect is that this Value property can only be assigned even numbers.

Note that this code is simplified for demonstration purposes. The real version doesn't initialize anything in the constructor; the two controls actually have control templates that do everything that's done in the respective constructors here. That is, in the real code, the outer control doesn't know the inner control.

When writing an even number into the text box and changing the focus (e.g. by focusing the dummy button below the text box), both Value properties get duly updated. When writing an odd number into the text box, however, the Value property of the inner control doesn't change, while the Value property of the outer control, as well as the TextBox.Text property, show the odd number.

My question is: How can I force an update in the text box (and ideally also in the outer control's Value property, while we're at it)?

I have found an SO question on the same problem, but doesn't really provide a solution. It alludes to using a property changed event handler to reset the value, but as far as I can see, that would mean duplicating the evaluation code to the outer control ... which is not really viable, as my actual evaluation code relies on some information basically only known (without much effort) to the inner control.

Moreover, this blogpost suggests invoking UpdateTarget on the binding in TextBox.Text in the CoerceValueCallback, but first, as implied above, my inner control cannot possibly have any knowledge about the text box, and second, I would probably have to call UpdateSource first on the binding of the Value property of the inner control. I don't see where to do that, though, as within the CoerceValue method, the coerced value has not yet been set (so it's too early to update the binding), while in the case that the value is reset by CoerceValue, the property value will just remain what it was, hence a property changed callback will not get invoked (as also implied in this discussion).

One possible workaround I had thought of was replacing the dependency property in SomeControl with a conventional property and an INotifyPropertyChanged implementation (so I can manually trigger the PropertyChanged event even if the value has been coerced). However, this would mean that I cannot declare a binding on that property any more, so it's not a really useful solution.

解决方案

I have been looking for an answer to this rather nasty bug myself for a while. One way to do it, without the need to force an UpdateTarget on the bindings is this:

  • Remove your CoerceValue callback.
  • Shift the logic of the CoerceValue callback into your ProcessValueChanged callback.
  • Assign your coerced value to your Value property, when applicable (when the number is odd)
  • You will end up with the ProcessValueChanged callback being hit twice, but your coerced value will end up being effectively pushed to your binding.

Base on your code, your dependency property declaration would become this:

public static readonly DependencyProperty ValueProperty = 
                       DependencyProperty.Register("Value",
                                                   typeof(int),
                                                   typeof(SomeControl),
                                                   new PropertyMetadata(0, ProcessValueChanged, null));

And then, your ProcessValueChanged would become this:

private static void ProcessValueChanged(object source, DependencyPropertyChangedEventArgs e)
    {
        int baseValue = (int) e.NewValue;
        SomeControl someControl = source as SomeControl;
        if (baseValue % 2 != 0) 
        {
            someControl.Value = DependencyProperty.UnsetValue;
        }
        else
        {
            someControl.ProcessValueChanged(e);
        }
    }

I slightly modified your logic, to prevent raising the event when the value needs to be coerced. As mentionned before, assigning to someControl.Value the coerced value will cause your ProcessValueChanged to be called twice in a row. Putting the else statement would only raise the events with valid values once.

I hope this helps!

这篇关于强制强制传播强制价值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆