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

C# 为什么我的数据绑定看到的是真实值而不是强制值?,c#,wpf,xaml,data-binding,dependency-properties,C#,Wpf,Xaml,Data Binding,Dependency Properties,我正在编写一个真正的NumericUpDown/Spinner控件,作为学习自定义控件编写的练习。我找到了我想要的大部分行为,包括适当的胁迫。然而,我的一次测试发现了一个缺陷 我的控件有3个依赖属性:值、最大值和最小值。我使用强制来确保值保持在最小值和最大值之间(包括最小值和最大值)。例如: // In NumericUpDown.cs public static readonly DependencyProperty ValueProperty = DependencyPropert

我正在编写一个真正的
NumericUpDown/Spinner
控件,作为学习自定义控件编写的练习。我找到了我想要的大部分行为,包括适当的胁迫。然而,我的一次测试发现了一个缺陷

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

// 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;
}
(对于控件的演示,我省略了xaml)

现在,如果我运行此命令,我会看到文本框中适当反映的
NumericUpDown
中的值,但是如果我键入超出范围的值,则超出范围的值将显示在测试文本框中,而
NumericUpDown
显示正确的值


这就是被强迫的价值观的作用方式吗?在ui中强制执行是很好的,但是我希望强制执行的值也会在数据绑定中运行。

您强制执行的是
v
,它是一个int类型的值。因此,它存储在堆栈上。它不以任何方式连接到
baseValue
。所以改变v不会改变baseValue

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

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


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

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

如果查看Reflector中的DependencyObject.SetValueCommon,可以在方法的中间看到对Expression.SetValue的调用。对UpdateEffectiveValue的调用将调用您的强制ValueCallback,该调用在绑定已经更新之后的最后

您也可以在框架类中看到这一点。从新的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>

如果拖动滑块,然后单击按钮,您将看到一条消息,如“值从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的非强制值更新绑定。

似乎绑定不支持concering val。您是否在TextBox中尝试了diff模式?如果从强制方法作为对象返回,它是否会被装箱?很有意思,因此这是预期的行为。我想我的下一步是找出如何提供一个可绑定的
ActualValue
属性,该属性反映了NumericUpDown中实际显示的内容。除了在属性更改的处理程序中重新评估强制并在那里设置
ActualValue
之外,您知道更好的方法吗?旧答案的新问题:)。我有这个问题,但是“GetBindingExpression”返回null。当值被绑定时,我试图强制!当用户设置该值时,它会起作用。所以一定有一些上下文问题(没有完全加载或其他)?有指针吗?如果您尝试在某个点执行
Value=x
,它将清除绑定。改用
SetCurrentValue
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; }
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), 
    new FrameworkPropertyMetadata(
      0, 
      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, 
      HandleValueChanged, 
      HandleCoerceValue,
      false
      UpdateSourceTrigger.Explicit));
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();
}