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

Posted

技术标签:

【中文标题】为啥我的数据绑定看到的是真实值而不是强制值?【英文标题】:Why does my data binding see the real value instead of the coerced value?为什么我的数据绑定看到的是真实值而不是强制值? 【发布时间】:2011-03-20 09:40:59 【问题描述】:

我正在编写一个真正的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 中强制它很好,但我希望强制值也能贯穿数据绑定。

【问题讨论】:

似乎绑定不支持考虑 val。你试过 TextBox 的 diff 模式吗? 【参考方案1】:

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

如果你查看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>

以及以下代码:

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; 

如果您拖动滑块然后单击按钮,您将收到类似“值已从 11 更改为 -1;滑块已从 11 更改为 10”的消息。

【讨论】:

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

旧问题的新答案::-)

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 的非强制值更新绑定。

【讨论】:

旧答案的新问题:)。我有这个问题,但“GetBindingExpression”返回空值。当值被绑定时,我试图强制!它在用户设置值时起作用。所以一定有一些上下文问题(没有完全加载或其他)?有什么指点吗? 如果您尝试在某个时候执行Value = x,那么它将清除绑定。请改用SetCurrentValue【参考方案3】:

您正在强制 v 这是一个 int 并且是这样的值类型。因此,它存储在堆栈中。它与baseValue 没有任何关系。所以改变 v 不会改变 baseValue。

同样的逻辑也适用于 baseValue。它是按值传递(不是按引用),因此更改它不会更改实际参数。

v 被返回,显然用于更新 UI。

您可能想研究将属性数据类型更改为引用类型。然后它将通过引用传递,所做的任何更改都将反映回源。假设数据绑定过程不创建副本。

【讨论】:

如果从强制方法返回为 Object 不会被装箱吗?

以上是关于为啥我的数据绑定看到的是真实值而不是强制值?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 SQL 服务器在我的表中插入 0 值而不是使用函数的正确值

统一的 Firebase 远程配置仅获取默认值而不是真实值

Laravel 插入默认值而不是通过请求发送的数据

为啥 ASP.Net MVC 模型绑定器将空 JSON 数组绑定到 null?

如果值被模型绑定器绑定,则应用所需属性

绑定一组数据集的行?