我已经构建了一个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.OverrideMetadata
和FrameworkElement.DataContextChanged
事件都会触发,因此原则上我可以禁用约束,但我不确定在什么条件下我可以安全地重新启用它们
我愿意假设所使用的DataContext
的初始约束值将遵守约束条件,并且如果上下文受到限制但我并不介意约束改变其值以与自身一致。但即使有了这些假设,我也找不到令人满意的解决方法。
我认为XAML中属性的顺序会影响它们更新的顺序。但无论我把它们放在哪个顺序,都存在一个潜在的问题。如果MaxValue
首先更新,则约束可能会在旧RangeMaxValue
上更改DataContext
;如果RangeMaxValue
首先更新,那么约束可以将其剪切到旧的上下文MaxValue
,更改新的DataContext
。
我可以完全从控件中删除约束,并在DataContext
中强制执行它们。这是有效的,但在我看来,约束的逻辑上正确的位置在控件中。
我可以从回调中删除直接强制执行,而是在ArrangeOverride
中应用约束(因为所有DependencyProperties
都有FrameworkPropertyMetadataOptions.AffectsArrange
)。控件不会重新排列,直到所有属性都发生了变化,因此原则上这应该有效(尽管这意味着跟踪一些额外状态以查看违反RangeMinValue <= RangeMaxValue
是否因为最小值增加或者最大值减少了,但感觉非常hacky。
将CoerceValueCallback
与PropertyChangedCallback
一起使用以触发手动重新强制。
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
跳跃以赶上它。
解决方法3是最好的选择,还是我错过了什么?