积极答复者
WPF,双向绑定的疑惑

问题
-
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日 13:22 ..
答案
-
这里就要先说明下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月18日 7:29
全部回复
-
其实你的逻辑是希望你的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锁屏而烦恼,赶紧下载这个 百变锁屏 应用,让你的锁屏不断地变化起来。 -
这里就要先说明下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锁屏而烦恼,赶紧下载这个 百变锁屏 应用,让你的锁屏不断地变化起来。- 已建议为答案 Jie BaoModerator 2015年11月17日 5:49
- 已编辑 Jie BaoModerator 2015年11月17日 5:50
-
这里就要先说明下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月18日 7:29
-
WPF没有开源,.Net开源了,http://referencesource.microsoft.com/。不过不知道有没有你需要的内容,因为我还没仔细搜索过。另外还有一个问题我没看懂,既然你已经使用了按钮事件,并且在其中对UI控件进行了直接操作,UI控件也都有Name值,那为什么还要使用绑定呢?为何不直接在代码中完成赋值过程?还是说这只是测试代码,还需要改动?能否简述一下你的目的?或者能否说一下为何必须要使用双向绑定并通过按钮事件来手动更新的原由?
是的,只是一个测试代码,一段说明问题的代码,就是为了搞懂双向绑定以及强制回调
-
感谢楼上的回复,也许只能解释到这种程度了。我想把这个问题延生一下。如下代码:
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:14 ...
-
正如我前面提过的,既然你反正是要处理Button的Click事件,那为何还要用绑定呢?直接在事件里处理数据,然后分别赋值给两个控件不就行了么。如果一定要用绑定的话,可以采用MVVM模式,也就是将数据部分单独提炼为数据模型,然后两个控件分别绑定数据模型,而不是控件之间相互绑定。
我的MainWindowViewModel只用来装了一些全局性的变量,使用MVVM的话,很小一个动作,都需要在MainWindowViewModel里面创建变量,不知MainWindowViewModel里面要装好多变量,而且有些时候用MVVM还更显复杂,维护起来很繁琐。
上面的代码只是举得例子,实际项目中,绑定目标实际上就是一个自定义TextBox,绑定源不是控件,是一个自定义对象,也没有Click事件。自定义TextBox除了在绑定的时候输入有限制,自身使用的时候,也要有输入限制。
- 已编辑 货郎大叔 2015年11月18日 23:19 ..