none
WPF,双向绑定的疑惑 RRS feed

  • 问题

  • public class CustomControl1 : ContentControl
    {
        static CustomControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
        }
        public static readonly DependencyProperty TextValueProperty = DependencyProperty.Register("TextValue", typeof(string), typeof(CustomControl1), 
            new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnTextValueChanged), new CoerceValueCallback(CoerceTextValue))); //注册依赖项属性
        public string TextValue
        {
            get { return (string)GetValue(TextValueProperty); }
            set { SetValue(TextValueProperty, value); }
        }
        private static void OnTextValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) //更改通知
        {
        }
        private static object CoerceTextValue(DependencyObject element, object value) //强制回调
        {
            return value.ToString() + "我";
        }
    }
    <Window x:Class="WPF1.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:WPF1"
            mc:Ignorable="d"
            Title="MainWindow" UseLayoutRounding="True" Height="350" Width="525">
        <Grid Name="bb">
            <local:CustomControl1 x:Name="cus" Height="30" Width="100" Background="#FFE9F1E5" TextValue="{Binding ElementName=textBlock, Path=Text, Mode=TwoWay, UpdateSourceTrigger=Explicit}" Margin="144,60,173,209"/>
            <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="20" Width="100" TextWrapping="Wrap" Text="123" VerticalAlignment="Top" Background="#FFFBE9D8" Margin="194,139,0,0"/>
            <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="391,162,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
        </Grid>
    </Window>



    private void button_Click(object sender, RoutedEventArgs e) { cus.TextValue = "598";

    BindingExpression bindingExpression = cus.GetBindingExpression(CustomControl1.TextValueProperty); //获取BindingExpression对象
        bindingExpression.UpdateSource(); //手动更新数据源 }

    在上面的代码中,一个自定义控件CustomControl1,定义了依赖项属性TextValue,在强制回调中,修改了属性值。

    在窗体代码中,CustomControl1的TextValue绑定到TextBlock的Text,并且是双向绑定。

    在Button的单击事件中,设置TextValue值为598,但这样的结果是,CustomControl1的TextValue值为"598我",而TextBlock的Text却为"598",为什么强制回调返回的值没有传输给数据源呢?





    2015年11月16日 11:35

答案

  • 这里就要先说明下CoeredValue的设计目的了,你也许看过一些资料,coered value设计主要是用来保护以来属性本身的值是合法的,也就是说它是保护本身Value的,在value修改时,强制回调进行检查和修改coered value 以得到DependencyObject 这个以来属性正确的属性值。

    假如是TextBlock绑定到CustomControl,TextBlock的Text属性值则是会使用强制回调返回的值.............
    这个时候这个依赖属性为绑定源, 他在给出值之前会经过回调调整后值然后给到绑定目标。

    Bob Bao

    Do you still use the same Windows 8 LockScreen always? Download Chameleon Win8 App quickly, that changes your LockScreen constantly.
    你是否还在看着一成不变的Windows 8锁屏而烦恼,赶紧下载这个 百变锁屏 应用,让你的锁屏不断地变化起来。

    2015年11月17日 6:16
    版主
  • ?????

    • 已建议为答案 Lymim 2015年11月18日 1:25
    • 已标记为答案 货郎大叔 2015年11月18日 7:28
    • 已编辑 [-] 2018年1月11日 13:51
    2015年11月17日 13:48
  • ?????
    • 已标记为答案 货郎大叔 2015年11月19日 8:08
    • 已编辑 [-] 2018年1月11日 13:52
    2015年11月19日 6:32

全部回复

  • 其实你的逻辑是希望你的CustomControl作为邦定源给TextBlock提供结果,但是你的邦定却是反的,TextBlock.Text作为了数据源。由于双向绑定在这,数据还是会由绑定目标更新到绑定源上,但是并不会走你的强制回调去取值,而是由绑定去将控件依赖属性值更新给了源。

    相反,绑定作用在TextBlock上,则可以工作:

    <wpfApplication1:CustomControl1 x:Name="cus" Height="30" Width="100" Background="#FFE9F1E5" TextValue="123" Margin="144,60,173,209"/>
    <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="20" Width="100" TextWrapping="Wrap" Text="{Binding ElementName=cus, Path=TextValue, Mode=TwoWay, UpdateSourceTrigger=Explicit}" VerticalAlignment="Top" Background="#FFFBE9D8" Margin="194,139,0,0"/>
    <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="391,162,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
    

    C#

          cus.TextValue = "598";
    
          BindingExpression bindingExpression = textBlock.GetBindingExpression(TextBlock.TextProperty); //获取BindingExpression对象
          bindingExpression.UpdateSource(); //手动更新数据源


    Bob Bao

    Do you still use the same Windows 8 LockScreen always? Download Chameleon Win8 App quickly, that changes your LockScreen constantly.
    你是否还在看着一成不变的Windows 8锁屏而烦恼,赶紧下载这个 百变锁屏 应用,让你的锁屏不断地变化起来。

    2015年11月17日 2:35
    版主
  • 我恰恰不是希望CustomControl作为邦定源给TextBlock提供结果,而是希望TextBlock给CustomControl提供结果,并且要求CustomControl将更改的属性返回给源,因此必须使用双向绑定。

    双向绑定中,目标属性的值为什么没有将强制回调返回的值传输给数据源呢?而恰恰你写的这段代码,假如是TextBlock绑定到CustomControl,属性值则是会使用强制回调返回的值.............

    2015年11月17日 4:48
  • 这里就要先说明下CoeredValue的设计目的了,你也许看过一些资料,coered value设计主要是用来保护以来属性本身的值是合法的,也就是说它是保护本身Value的,在value修改时,强制回调进行检查和修改coered value 以得到DependencyObject 这个以来属性正确的属性值。

    但是他并不保护绑定源的值,因为值是从源来的,它会去检查源来的值来强制修改给自己以来属性;而这个动作发生在双向数据回给绑定源之后,所以他不会理睬绑定源是否安全有效,这个时候绑定源还是原来的值。  但是你去查看控件以来属性时候,他已经被强制回调修改了。

    我曾经看过论坛里的一个讨论, 你也可以看下,会有比较深的理解:https://social.msdn.microsoft.com/Forums/vstudio/en-US/0224a9f0-b5cf-482e-b284-0aaf951175b8/coercevaluecallback-is-called-after-twoway-bound-dp-is-updated


    Bob Bao

    Do you still use the same Windows 8 LockScreen always? Download Chameleon Win8 App quickly, that changes your LockScreen constantly.
    你是否还在看着一成不变的Windows 8锁屏而烦恼,赶紧下载这个 百变锁屏 应用,让你的锁屏不断地变化起来。


    2015年11月17日 5:49
    版主
  • 这里就要先说明下CoeredValue的设计目的了,你也许看过一些资料,coered value设计主要是用来保护以来属性本身的值是合法的,也就是说它是保护本身Value的,在value修改时,强制回调进行检查和修改coered value 以得到DependencyObject 这个以来属性正确的属性值。

    假如是TextBlock绑定到CustomControl,TextBlock的Text属性值则是会使用强制回调返回的值.............
    2015年11月17日 5:59
  • 这里就要先说明下CoeredValue的设计目的了,你也许看过一些资料,coered value设计主要是用来保护以来属性本身的值是合法的,也就是说它是保护本身Value的,在value修改时,强制回调进行检查和修改coered value 以得到DependencyObject 这个以来属性正确的属性值。

    假如是TextBlock绑定到CustomControl,TextBlock的Text属性值则是会使用强制回调返回的值.............
    这个时候这个依赖属性为绑定源, 他在给出值之前会经过回调调整后值然后给到绑定目标。

    Bob Bao

    Do you still use the same Windows 8 LockScreen always? Download Chameleon Win8 App quickly, that changes your LockScreen constantly.
    你是否还在看着一成不变的Windows 8锁屏而烦恼,赶紧下载这个 百变锁屏 应用,让你的锁屏不断地变化起来。

    2015年11月17日 6:16
    版主
  • 你给的链接,我已经使用翻译工具看了,也没有说出个什么所以然,我也不想使用ValidationRules。

    2015年11月17日 6:16
  • 这个是不是与TextBox的Text依赖项属性有关,此属性通常会在TextBox失去焦点时才触发,用代码赋值时可能有异步延迟。.Net已开源,所以最好去看看源代码。
    WPF开源了吗?在哪里查看?
    2015年11月17日 11:56
  • WPF没有开源,.Net开源了,http://referencesource.microsoft.com/。不过不知道有没有你需要的内容,因为我还没仔细搜索过。另外还有一个问题我没看懂,既然你已经使用了按钮事件,并且在其中对UI控件进行了直接操作,UI控件也都有Name值,那为什么还要使用绑定呢?为何不直接在代码中完成赋值过程?还是说这只是测试代码,还需要改动?能否简述一下你的目的?或者能否说一下为何必须要使用双向绑定并通过按钮事件来手动更新的原由?

    是的,只是一个测试代码,一段说明问题的代码,就是为了搞懂双向绑定以及强制回调

    2015年11月17日 13:05
  • ?????

    • 已建议为答案 Lymim 2015年11月18日 1:25
    • 已标记为答案 货郎大叔 2015年11月18日 7:28
    • 已编辑 [-] 2018年1月11日 13:51
    2015年11月17日 13:48
  • 感谢楼上的回复,也许只能解释到这种程度了。我想把这个问题延生一下。如下代码:

    public class CustomControl1 : ContentControl
    {
        static CustomControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
        }
        public static readonly DependencyProperty TextValueProperty = DependencyProperty.Register("TextValue", typeof(string), typeof(CustomControl1), 
            new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnTextValueChanged), new CoerceValueCallback(CoerceTextValue))); //注册依赖项属性
        public string TextValue
        {
            get { return (string)GetValue(TextValueProperty); }
            set { SetValue(TextValueProperty, value); }
        }
        private static void OnTextValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) //更改通知
        {
            CustomControl1 cus = obj as CustomControl1;
            //cus.TextValue = args.NewValue.ToString();  //放在这里,恐怕不行
        }
        private static object CoerceTextValue(DependencyObject element, object value) //强制回调
        {
            int Number;
            if(!int.TryParse(value.ToString(),out Number))
            {
                return "";
            }
            if (Number > 100)
            {
                return "100";
            }
            return Number.ToString();
        }
    }
    <Window x:Class="WPF1.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:WPF1"
            mc:Ignorable="d"
            Title="MainWindow" UseLayoutRounding="True" Height="350" Width="525">
        <Grid Name="bb">
            <local:CustomControl1 x:Name="cus" Height="30" Width="100" Background="#FFE9F1E5" TextValue="{Binding ElementName=textBlock, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="144,60,173,209"/>
            <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="20" Width="100" TextWrapping="Wrap" Text="123" VerticalAlignment="Top" Background="#FFFBE9D8" Margin="194,139,0,0"/>
            <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="391,162,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
        </Grid>
    </Window>
    private void button_Click(object sender, RoutedEventArgs e)
    {
        cus.TextValue = "598";
    }

    在这里,UpdateSourceTrigger被设置成了PropertyChanged,单击按钮后,TextValue变成了100,TextBlock则是598,无疑这个结果没有值得怀疑。但是,我现在的项目需要数据源始终要保持与绑定目标的属性值一致,换句话说,当修改目标属性的时候,我需要数据源也得到强制回调那样的限制,不能有无效的值,该怎么办呢?

    要实现这个功能,也许可以使用值转换器达到这个目的,但是有点代码重复,需要在转换器中写上与强制回调类似的代码。于是,我干脆对TextValue再赋一次值。如下:

    private void button_Click(object sender, RoutedEventArgs e)
    {
        cus.TextValue = "598";
        cus.TextValue = cus.TextValue;
    }

    最终,这样就达到了我的目的。

    不过,我觉得这样写的代码不够优雅,你有更优雅的写法吗?




    2015年11月18日 15:09
  • 正如我前面提过的,既然你反正是要处理Button的Click事件,那为何还要用绑定呢?直接在事件里处理数据,然后分别赋值给两个控件不就行了么。如果一定要用绑定的话,可以采用MVVM模式,也就是将数据部分单独提炼为数据模型,然后两个控件分别绑定数据模型,而不是控件之间相互绑定。

     我的MainWindowViewModel只用来装了一些全局性的变量,使用MVVM的话,很小一个动作,都需要在MainWindowViewModel里面创建变量,不知MainWindowViewModel里面要装好多变量,而且有些时候用MVVM还更显复杂,维护起来很繁琐。

    上面的代码只是举得例子,实际项目中,绑定目标实际上就是一个自定义TextBox,绑定源不是控件,是一个自定义对象,也没有Click事件。自定义TextBox除了在绑定的时候输入有限制,自身使用的时候,也要有输入限制。



    2015年11月18日 21:43
  • ?????
    • 已标记为答案 货郎大叔 2015年11月19日 8:08
    • 已编辑 [-] 2018年1月11日 13:52
    2015年11月19日 6:32
  • 在这种情况下就更得使用MVVM了,控件间的绑定是很受限制的,性能也很糟糕。实际上复杂的绑定,特别是双向和带转换器的绑定性能都不怎么样。如果无需使用MVVM,那最好还是使用事件/自定义事件等代码方式来实现。

    好吧,感谢你的回复。再见
    2015年11月19日 8:08