代码之家  ›  专栏  ›  技术社区  ›  Greg D

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

  •  12
  • Greg D  · 技术社区  · 14 年前

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

    我的控件有3个依赖属性: Value , MaximumValue MinimumValue . 我用胁迫来确保 价值 保持在最小值和最大值之间,包括最小值和最大值。例如。:

    // 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 适当地反映在文本框中,但如果我键入的值超出范围,则超出范围的值将显示在测试文本框中,而 数字旋转控件 显示正确的值。

    强制值的作用方式是这样的吗?在UI中强制它是很好的,但是我希望强制值也能通过数据绑定运行。

    3 回复  |  直到 9 年前
        1
  •  11
  •   Quartermeister    14 年前

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

    如果查看Reflector中的DependencyObject.setValueCommon,可以在方法的中途看到对Expression.setValue的调用。对将调用强制值回调的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”。

        2
  •  5
  •   Fratyx    9 年前

    旧问题的新答案::-)

    在注册时 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));
    

    现在的绑定源 价值属性 不会在自动更新 PropertyChanged . 在您的 HandleValueChanged 方法(参见上面的代码)。 只有在调用强制方法之后才对属性的“real”更改调用此方法。

    你可以这样做:

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

        3
  •  -1
  •   Philip Smith    14 年前

    你是在胁迫 v 它是一个int,因此是一个值类型。因此,它存储在堆栈上。它决不会连接到 baseValue . 所以改变v不会改变基值。

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

    V 返回并明确用于更新用户界面。

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