如何在更改DataContext

时间:2016-11-09 12:59:30

标签: wpf data-binding wpf-controls

我已经构建了一个WPF UserControl,它是一个允许选择一系列值的滑块。它具有约束的四个属性

MinValue <= RangeMinValue <= RangeMaxValue <= MaxValue

在YAGNI的基础上,我目前将MinValue强制为0,其他三个使用PropertyChangedCallback的依赖属性来强制执行约束:

    public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(nameof(MaxValue), typeof(int), typeof(RangeTrack),
        new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, _EnforceConstraints));

    public int MaxValue { get { return (int)GetValue(MaxValueProperty); } set { SetValue(MaxValueProperty, value); } }

    public static readonly DependencyProperty RangeMinValueProperty = DependencyProperty.Register(nameof(RangeMinValue), typeof(int), typeof(RangeTrack),
        new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, _EnforceConstraints));

    public int RangeMinValue { get { return (int)GetValue(RangeMinValueProperty); } set { SetValue(RangeMinValueProperty, value); } }

    public static readonly DependencyProperty RangeMaxValueProperty = DependencyProperty.Register(nameof(RangeMaxValue), typeof(int), typeof(RangeTrack),
        new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, _EnforceConstraints));

    public int RangeMaxValue { get { return (int)GetValue(RangeMaxValueProperty); } set { SetValue(RangeMaxValueProperty, value); } }

    // Enforce the constraints MinValue <= RangeMinValue <= RangeMaxValue <= MaxValue
    private static void _EnforceConstraints(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var track = (RangeTrack)d;
        var newValue = (int)e.NewValue;

        // We work on the basis that property changes will cascade, so if the constraints
        // are violated we make one change which will cascade to tidy up the rest.

        if (e.Property == MaxValueProperty)
        {
            // This should be the only test which doesn't involve adjacent values.
            if (newValue < track.MinValue) { track.MaxValue = track.MinValue; return; }
            if (newValue < track.RangeMaxValue) { track.RangeMaxValue = newValue; return; }
        }

        if (e.Property == RangeMaxValueProperty)
        {
            if (newValue > track.MaxValue) { track.RangeMaxValue = track.MaxValue; return; }
            if (newValue < track.RangeMinValue) { track.RangeMinValue = newValue; return; }
        }

        if (e.Property == RangeMinValueProperty)
        {
            if (newValue < track.MinValue) { track.RangeMinValue = track.MinValue; return; }
            if (newValue > track.RangeMaxValue) { track.RangeMaxValue = newValue; return; }
        }
    }

该控件完全孤立地工作,但是现实生活中存在问题&#34;用法:

    <my:RangeSlider MaxValue="{Binding NumFoos}"
                    RangeMinValue="{Binding StartFoo}"
                    RangeMaxValue="{Binding EndFoo}" />

当(继承的)DataContext更改时,绑定会更新,但不会同时更新。结果:_EnforceConstraints回调将在某个点处被调用,其中某些值是从旧DataContext的绑定中获得的,而某些值来自新DataContext,从而导致意外更改。

绑定更新之前,DataContextProperty.OverrideMetadataFrameworkElement.DataContextChanged事件都会触发,因此原则上我可以禁用约束,但我不确定在什么条件下我可以安全地重新启用它们

拒绝解决方法

我愿意假设所使用的DataContext的初始约束值将遵守约束条件,并且如果上下文受到限制但我并不介意约束改变其值以与自身一致。但即使有了这些假设,我也找不到令人满意的解决方法。

  1. 我认为XAML中属性的顺序会影响它们更新的顺序。但无论我把它们放在哪个顺序,都存在一个潜在的问题。如果MaxValue首先更新,则约束可能会在旧RangeMaxValue上更改DataContext;如果RangeMaxValue首先更新,那么约束可以将其剪切到旧的上下文MaxValue,更改新的DataContext

  2. 我可以完全从控件中删除约束,并在DataContext中强制执行它们。这是有效的,但在我看来,约束的逻辑上正确的位置在控件中。

  3. 我可以从回调中删除直接强制执行,而是在ArrangeOverride中应用约束(因为所有DependencyProperties都有FrameworkPropertyMetadataOptions.AffectsArrange)。控件不会重新排列,直到所有属性都发生了变化,因此原则上这应该有效(尽管这意味着跟踪一些额外状态以查看违反RangeMinValue <= RangeMaxValue是否因为最小值增加或者最大值减少了,但感觉非常hacky。

  4. CoerceValueCallbackPropertyChangedCallback一起使用以触发手动重新强制。

    private static void _MaxValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs evt)
    {
        // Cascade: MaxValue directly impacts on RangeMaxValue, which directly impacts on RangeMinValue, so they must be coerced in that order.
        d.CoerceValue(RangeMaxValueProperty);
        d.CoerceValue(RangeMinValueProperty);
    }
    
    private static object _CoerceMaxValue(DependencyObject d, object baseValue)
    {
        var track = (RangeTrack)d;
        ValueType value = (ValueType)baseValue;
    
        if (value < track.MinValue) value = track.MinValue;
        return value;
    }
    
    private static void _RangeMaxValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs evt)
    {
        d.CoerceValue(RangeMinValueProperty);
    }
    
    private static object _CoerceRangeMaxValue(DependencyObject d, object baseValue)
    {
        var track = (RangeTrack)d;
        ValueType value = (ValueType)baseValue;
    
        // NB We might have track.RangeMinValue > track.MaxValue at certain points in the coercion cascade.
        if (value < track.RangeMinValue) value = track.RangeMinValue;
        if (value > track.MaxValue) value = track.MaxValue;
        return value;
    }
    
    private static void _RangeMinValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs evt)
    {
        d.CoerceValue(RangeMaxValueProperty);
    }
    
    private static object _CoerceRangeMinValue(DependencyObject d, object baseValue)
    {
        var track = (RangeTrack)d;
        ValueType value = (ValueType)baseValue;
    
        if (value > track.RangeMaxValue) value = track.RangeMaxValue;
        if (value < track.MinValue) value = track.MinValue;
        return value;
    }
    

    这里的问题是,如果我试图&#34;推&#34;通过拖动RangeMaxValue的拇指来移动RangeMinValue的拇指,但是如果我然后移动RangeMaxValue拇指,那么RangeMinValue跳跃以赶上它。

  5. 解决方法3是最好的选择,还是我错过了什么?

0 个答案:

没有答案