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

Gre*_*g D 13 c# data-binding wpf xaml dependency-properties

我正在编写一个真正的NumericUpDown/Spinner控件作为练习来学习自定义控件创作.我已经得到了我正在寻找的大部分行为,包括适当的强制.然而,我的一项测试显示出一个缺陷.

我的控制有3个依赖属性:Value,MaximumValue,和MinimumValue.我使用强制来确保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;
}
Run Code Online (Sandbox Code Playgroud)

我的测试只是为了确保数据绑定符合我的预期.我创建了一个默认的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>
Run Code Online (Sandbox Code Playgroud)

非常简单的代码隐藏:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

(我省略了控件演示的xaml)

现在如果我运行它,我会NumericUpDown在文本框中看到适当反映的值,但是如果我输入的值超出范围,则超出范围值会显示在测试文本框中,同时NumericUpDown显示正确的值.

这是强制价值应该如何行动的吗?在ui中强制它是好的,但我也期望强制值也能通过数据绑定.

Qua*_*ter 13

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

如果查看Reflector中的DependencyObject.SetValueCommon,可以在方法的中途看到对Expression.SetValue的调用.在绑定已经更新之后,对调用CoerceValueCallback的UpdateEffectiveValue的调用就在最后.

您也可以在框架类上看到这一点.从新的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>
Run Code Online (Sandbox Code Playgroud)

和以下代码:

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; }
Run Code Online (Sandbox Code Playgroud)

如果您拖动滑块然后单击按钮,您将收到一条消息,如"值从11更改为-1;滑块从11更改为10".

  • 有趣,所以这是预期的行为。我想下一步是弄清楚如何提供一个可绑定的“ ActualValue”属性,以反映NumericUpDown中实际显示的内容。您是否知道有一种更好的方法,而不仅仅是重新评估属性更改处理程序中的强制并在那里设置“ ActualValue”? (2认同)

Fra*_*tyx 5

旧问题的新答案:-)

在登记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));
Run Code Online (Sandbox Code Playgroud)

现在,绑定源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();
}
Run Code Online (Sandbox Code Playgroud)

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