强制传播强制价值

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


我在WPF依赖项属性上使用了CoerceValueCallback ,我坚持强制值不传播到绑定的问题。

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>

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

内部控件有一个CoerceValueCallback ,它的Value属性被注册,其效果是这个Value属性只能被赋予偶数。

请注意,为了演示目的,此代码被简化。 真正的版本不会在构造函数中初始化任何东西; 这两个控件实际上都有控制模板,它们完成了这里各个构造函数中所做的所有事情。 也就是说,在真实的代码中,外部控制不知道内部控制。

当在文本框中输入偶数并改变焦点时(例如,通过将文本框下方的虚拟按钮聚焦),两个Value属性都会得到适时更新。 但是,在向文本框中写入奇数时,内部控件的Value属性不会更改,而外部控件的Value属性以及TextBox.Text属性会显示奇数。

我的问题是: 如何在文本框中强制更新(理想情况下,外部控件的Value属性也是如此)。

我在同一个问题上发现了一个SO问题,但并没有真正提供解决方案。 它暗示使用属性更改事件处理程序来重置值,但据我所知,这意味着将评估代码复制到外部控制...这不是真正可行的,因为我的实际评估代码依赖于某些基本上只知道(没有太多努力)内部控制的信息。

此外,本UpdateTarget建议在CoerceValueCallback中的TextBox.Text中的绑定上调用UpdateTarget ,但首先,如上所述,我的内部控件不可能对文本框有任何了解,其次,我可能必须首先调用UpdateSource内部控件的Value属性的绑定。 然而,我不知道该怎么做,因为在CoerceValue方法中,强制值尚未设置(因此更新绑定还为时过早),而在由CoerceValue重置值的情况下,属性值将保持原来的状态,因此属性改变的回调不会被调用(这也在本次讨论中暗示)。

我想到的一种可能的解决方法是用常规属性和INotifyPropertyChanged实现替换SomeControl的依赖项属性(因此,即使值已被强制,我也可以手动触发PropertyChanged事件)。 但是,这意味着我不能再声明对该属性的绑定,所以它不是一个真正有用的解决方案。


我一直在寻找对这个相当讨厌的错误的答案。 一种方法来做到这一点,而不需要强制绑定上的UpdateTarget是这样的:

  • 删除您的CoerceValue回调。
  • 将CoerceValue回调的逻辑转换为您的ProcessValueChanged回调。
  • 适用时(当数字为奇数时)将强制值分配给Value属性
  • 你最终会得到两次ProcessValueChanged回调,但是你的强制值最终会被有效地推到你的绑定。
  • 根据你的代码,你的依赖属性声明将变成这样:

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

    然后,你的ProcessValueChanged会变成这样:

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

    我略微修改了你的逻辑,以防止在需要强制值时引发事件。 正如之前提到的,赋值给someControl.Value强制值将导致您的ProcessValueChanged连续调用两次。 放入else语句只会引发一次有效值的事件。

    我希望这有帮助!

    链接地址: http://www.djcxy.com/p/11571.html

    上一篇: Force Propagation of Coerced Value

    下一篇: Decode a video file from memory using libav