为什么我的数据绑定看到的是真实值而不是强制值?

我正在编写一个真正的NumericUpDown/Spinner控件作为学习自定义控件创作的练习。 我已经掌握了大部分我正在寻找的行为,包括适当的强制措施。 然而,我的一项测试揭示了一个缺陷。

我的控制有3个依赖属性: ValueMaximumValueMinimumValue 。 我使用强制来确保Value保持在最小值和最大值之间,包括最小值和最大值。 例如:

// In NumericUpDown.cs

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), 
    new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, HandleValueChanged, HandleCoerceValue));

[Localizability(LocalizationCategory.Text)]
public int Value
{
    get { return (int)this.GetValue(ValueProperty); }
    set { this.SetCurrentValue(ValueProperty, value); }
}

private static object HandleCoerceValue(DependencyObject d, object baseValue)
{
    NumericUpDown o = (NumericUpDown)d;
    var v = (int)baseValue;

    if (v < o.MinimumValue) v = o.MinimumValue;
    if (v > o.MaximumValue) v = o.MaximumValue;

    return v;
}

我的测试只是为了确保数据绑定的工作方式符合我的期望。 我创建了一个默认的wpf windows应用程序,并将其引入以下xaml:

<Window x:Class="WpfApplication.MainWindow" x:Name="This"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:nud="clr-namespace:WpfCustomControlLibrary;assembly=WpfCustomControlLibrary"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <nud:NumericUpDown Value="{Binding ElementName=This, Path=NumberValue}"/>
        <TextBox Grid.Row="1" Text="{Binding ElementName=This, Path=NumberValue, Mode=OneWay}" />
    </Grid>
</Window>

非常简单的代码隐藏:

public partial class MainWindow : Window
{
    public int NumberValue
    {
        get { return (int)GetValue(NumberValueProperty); }
        set { SetCurrentValue(NumberValueProperty, value); }
    }

    // Using a DependencyProperty as the backing store for NumberValue.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty NumberValueProperty =
        DependencyProperty.Register("NumberValue", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));     

    public MainWindow()
    {
        InitializeComponent();
    }
}

(我省略了控件呈现的xaml)

现在,如果我运行此操作,则会看到NumericUpDown的值在文本框中正确反映,但如果键入的值超出范围,则在NumericUpDown显示正确值时,超出范围值显示在测试文本框中。

这是如何强制价值观应该采取行动? 它在UI中被强制是好的,但我也希望被强制的值也能通过数据绑定。


哇,这是令人惊讶的。 在依赖项属性上设置值时,绑定表达式会在值强制运行前更新!

如果您查看Reflector中的DependencyObject.SetValueCommon,则可以在该方法的中途看到对Expression.SetValue的调用。 在绑定已更新后,调用UpdateEffectiveValue来调用CoerceValueCallback的操作即将结束。

你也可以在框架类中看到它。 从新的WPF应用程序添加以下XAML:

<StackPanel>
    <Slider Name="Slider" Minimum="10" Maximum="20" Value="{Binding Value, 
        RelativeSource={RelativeSource AncestorType=Window}}"/>
    <Button Click="SetInvalid_Click">Set Invalid</Button>
</StackPanel>

和下面的代码:

private void SetInvalid_Click(object sender, RoutedEventArgs e)
{
    var before = this.Value;
    var sliderBefore = Slider.Value;
    Slider.Value = -1;
    var after = this.Value;
    var sliderAfter = Slider.Value;
    MessageBox.Show(string.Format("Value changed from {0} to {1}; " + 
        "Slider changed from {2} to {3}", 
        before, after, sliderBefore, sliderAfter));
}

public int Value { get; set; }

如果拖动Slider然后单击该按钮,则会看到类似“值从11更改为-1;滑块从11更改为10”的消息。


一个旧问题的新答案::-)

ValueProperty的注册中使用了一个FrameworkPropertyMetadata实例。 将此实例的UpdateSourceTrigger属性设置为Explicit 。 这可以在构造函数重载中完成。

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), 
    new FrameworkPropertyMetadata(
      0, 
      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, 
      HandleValueChanged, 
      HandleCoerceValue,
      false
      UpdateSourceTrigger.Explicit));

现在, ValueProperty的绑定源将不会在PropertyChanged自动更新。 在HandleValueChanged方法中手动执行更新(请参阅上面的代码)。 该方法仅在强制方法被调用后的属性的“真实”更改时调用。

你可以这样做:

static void HandleValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown nud = obj as NumericUpDown;
    if (nud == null)
        return;

    BindingExpression be = nud.GetBindingExpression(NumericUpDown.ValueProperty);
    if(be != null)
        be.UpdateSource();
}

通过这种方式,您可以避免使用DependencyProperty的非强制值更新绑定。


你强制v是一个int类型,并且是一个这样的值类型。 因此它被存储在堆栈中。 它没有连接到baseValue 。 所以改变v不会改变baseValue。

相同的逻辑适用于baseValue。 它通过值传递(而不是通过引用),所以改变它不会改变实际参数。

v被返回,并且明确用于更新UI。

您可能需要研究将属性数据类型更改为引用类型。 然后它会通过引用传递,并且所做的任何更改都会反射回来。 假定数据绑定过程不会创建副本。

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

上一篇: Why does my data binding see the real value instead of the coerced value?

下一篇: Libav (ffmpeg) copying decoded video timestamps to encoder