简述:强制转换的值在数据绑定中不会传递。当代码后台不知道绑定的另一侧时,如何强制更新数据绑定?
我正在使用CoerceValueCallback
对WPF依赖属性进行操作,但我卡在了一个问题上,强制转换的值无法通过绑定传播到其他地方。
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”属性。
内部控件的“Value”属性已注册了一个“CoerceValueCallback”,其效果是该“Value”属性只能被分配为偶数。
请注意,此代码为演示目的简化。真实版本不会在构造函数中初始化任何内容;两个控件实际上有控件模板,在各自的构造函数中完成所有操作。也就是说,在实际代码中,外部控件不知道内部控件。
将偶数写入文本框并更改焦点(例如通过聚焦文本框下面的虚拟按钮),两个“Value”属性都会得到适当的更新。然而,将奇数写入文本框时,内部控件的“Value”属性不会更改,而外部控件的“Value”属性和“TextBox.Text”属性显示奇数。
我的问题是:我如何强制更新文本框(理想情况下还要更新外部控件的“Value”属性)?
我发现与此问题相同的 SO 问题,但没有真正提供解决方案。它暗示使用属性更改事件处理程序重置值,但据我所见,这意味着在外部控件中复制计算代码...这不是很可行,因为我的实际计算代码依赖于一些基本上仅已知(不需要太多努力)的内部控件信息。
此外,这篇博客文章建议在CoerceValueCallback
中对 TextBox.Text
的绑定调用UpdateTarget
方法。但是首先,如上所述,我的内部控件不可能有关于文本框的任何了解;其次,我可能需要先在内部控件的Value
属性的绑定上调用UpdateSource
方法。然而,在 CoerceValue
方法中,被强制转换的值尚未设置(所以更新绑定还为时过早),而如果该值被 CoerceValue
重置,则属性值将保持不变,因此属性更改回调也不会被调用(正如这个讨论中所示)。我曾考虑过一种可能的解决方法,即使用常规属性和
INotifyPropertyChanged
实现来替换 SomeControl
中的依赖属性(这样即使在值被强制转换时也可以手动触发 PropertyChanged
事件)。然而,这意味着我不能再声明该属性的绑定,所以这不是一个非常有用的解决方案。