美文网首页
绑定值到有CoerceValueCallback的Depende

绑定值到有CoerceValueCallback的Depende

作者: 勿念情 | 来源:发表于2018-08-28 18:11 被阅读0次

2018-9-19 之前的理解有误,在文章后面有进行补充


阅读本文前,请确保对DependencyProperty有一定的了解

DependencyProperty在WPF中被广泛使用。而使用CoerceValueCallback进行数据校正的控件也不在少数,比如常见的Slider。很多带有最大值和最小值的控件,都使用CoerceValueCallback进行校验。

一般的情况下,使用这些控件不会出现问题。今天举一个特殊的例子,来探讨一下CoerceValueCallback和Binding之间的奥秘。
首先自定义一个控件,这个控件有一个属性是使用回调校验数值的。回调中我随便写了两个数来表示最大值和最小值。

<UserControl x:Class="WpfApp1.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <StackPanel>
            <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=AncestorType=local:UserControl1},Path=Value}"/>
            <TextBox TextChanged="TextBox_TextChanged"/>
        </StackPanel>
    </Grid>
</UserControl>
    public partial class UserControl1 : UserControl
    {
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(UserControl1),
            new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPropertyChanged, CoerceValue));

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }

        private static object CoerceValue(DependencyObject d, object baseValue)
        {
            var value = (double)baseValue;
            if (value < 600)
                value = 600;
            else if (value > 5000)
                value = 5000;
            return value;
        }

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

        public UserControl1()
        {
            InitializeComponent();
        }

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCurrentValue(ValueProperty, double.Parse((sender as TextBox).Text));
        }
    }

非常简单的一个控件,声明的依赖属性也是很常规的声明方式,相信很多朋友已经写了不下千八百变了。

现在来使用这个控件。这里我用了两个TextBlock来分别显示ViewModel的值和自定义控件的值。

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <local:UserControl1 Value="{Binding Value}" x:Name="test"/>
            <TextBlock Text="{Binding Value}"/>
            <TextBlock Text="{Binding ElementName=test,Path=Value}"/>
        </StackPanel>
    </Grid>
</Window>
namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ViewModel();
        }
    }

    public class ViewModel//:DependencyObject
    {
        public double Value { get; set; }
    }
}

好了,见证奇迹的时刻了。


神奇的一幕出现了

我们输入了一个远超5000的数,而从结果来看,ViewModel的值是我们输入的值,而控件的值,是校验之后的值。而ViewModel的值是直接绑定到控件之上的,不得不说,很诡异。
通过调试(请读者自行尝试),我们可以发现,往ViewModel设置值是先于回调校验的,而校验完成之后,并没有再次往ViewModel去设置值。

StackOverFlow上有大神看了源码,说源码就是这样的,调用的逻辑就是现在我们看到的逻辑,没毛病。感兴趣的同学可以去研究一下Binding的源码。

不管怎么样,我们希望的是控件的值和绑定的ViewModel的值是一致的。所以看起来最科学最直接的方式不能满足我们的需求,那就只能想想别的办法了。幸运的是,在CoerceValueCallback完成之后,才会去调用PropertyChangedCallback。所以我们可以在PropertyChangedCallback里面手动的去更新绑定值。
修改一下UserControl1的后台代码。

    public partial class UserControl1 : UserControl
    {
        //修改触发方式为手动触发
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(UserControl1),
            new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPropertyChanged, CoerceValue, false, UpdateSourceTrigger.Explicit));

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //手动触发更新
            var control = d as UserControl1;
            BindingExpression binding = control.GetBindingExpression(UserControl1.ValueProperty);
            binding?.UpdateSource();
        }

        private static object CoerceValue(DependencyObject d, object baseValue)
        {
            var value = (double)baseValue;
            if (value < 600)
                value = 600;
            else if (value > 5000)
                value = 5000;
            return value;
        }

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

        public UserControl1()
        {
            InitializeComponent();
        }

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCurrentValue(ValueProperty, double.Parse((sender as TextBox).Text));
        }
    }

好了,我们打上断点一步步调试过去。

  • 没有在CoerceValueCallback之前去设置ViewModel的值,很好。
  • CoerceValueCallback之后调用PropertyChangedCallback,很好,值也是对的。
  • 好,UpdateSource
  • 欢声笑语中打出GG


    然并卵

    绝望的发现,即使是控件的Value值是正确的情况下,UpdateSource依旧设置的是校验之前的值到ViewModel。没错,巨硬就是这么犀利。
    只能假设,UpdateSource就是设置的是校验之前的值,那么,把校验之后的值直接往属性上设置,应该就能解决问题了。按这个思路尝试一下。

    public partial class UserControl1 : UserControl
    {
        //修改触发方式为手动触发
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(UserControl1),
            new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPropertyChanged, CoerceValue, false, UpdateSourceTrigger.Explicit));

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //手动触发更新
            var control = d as UserControl1;
            //重新设置一遍
            control.SetCurrentValue(ValueProperty, e.NewValue);
            BindingExpression binding = control.GetBindingExpression(UserControl1.ValueProperty);
            binding?.UpdateSource();
        }

        private static object CoerceValue(DependencyObject d, object baseValue)
        {
            var value = (double)baseValue;
            if (value < 600)
                value = 600;
            else if (value > 5000)
                value = 5000;
            return value;
        }

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

        public UserControl1()
        {
            InitializeComponent();
        }

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCurrentValue(ValueProperty, double.Parse((sender as TextBox).Text));
        }
    }
大功告成

哎,也不知道这算不算Bug。


2018-9-19 补充

前文中提到的校验导致了View和ViewModel之前数据不同的情况,其实应该是使用不当。
如果你的数据需要进行校验,应当在ViewModel中进行。CoerceValue应当是用来对View界面的一个约束,是为了保证在后台数据错误的情况下,前台界面显示不会出错。不应CoerceValue中做后台数据校验工作。

相关文章

  • 绑定值到有CoerceValueCallback的Depende

    2018-9-19 之前的理解有误,在文章后面有进行补充 阅读本文前,请确保对DependencyProperty...

  • 基本语法

    双花括号语法是 Angular 的插值绑定语法。 这个插值绑定的意思是把组件的 title 属性的值绑定到 HTM...

  • 08Angular双向绑定

    双向绑定到底是什么 属性绑定+事件绑定 [value]="username" ——绑定username值到inpu...

  • js 数据双向绑定

    数据绑定: 在页面中,绑定两个或多个元素之间的值,例如将input的值绑定到h1上,改变input输入框的值,h1...

  • VUE的学习记录(3)

    今天学习数据绑定,插值 Vue的视图模板基于DOM实现,通过数据绑定将App组件上定义的数据模型绑定到模板上插值是...

  • WPF 数据绑定(一)

    最基本的绑定 将Text 的文本绑定到Window的Background属性,设置双向绑定,修改文本的值,改变Gr...

  • Ionic ng-model 数据双向绑定

    ionic 使用AngularJS ng-model 实现数据双向绑定 AngularJS 实例 绑定输入框的值到...

  • vue 之 input 的value绑定

    vue双向绑定值 vue单向绑定值

  • Swift版本 ReactiveCocoa5.0.3 基本用法

    一.UI绑定 <~ 符号用法 1.某个值动态绑定到UITextField控件上来 var firstName: M...

  • 数据的绑定 - - - 单项绑定

    数据单项绑定:把模型的数据展示到视图当中 绑定的方式有三种1.使用插值语法:{{模型属性}} 加载时,会造成闪烁。...

网友评论

      本文标题:绑定值到有CoerceValueCallback的Depende

      本文链接:https://www.haomeiwen.com/subject/sgyqwftx.html