为什么我的数据绑定看到的是真实值而不是强制值?
我正在编写一个真正的NumericUpDown/Spinner
控件作为学习自定义控件创作的练习。 我已经掌握了大部分我正在寻找的行为,包括适当的强制措施。 然而,我的一项测试揭示了一个缺陷。
我的控制有3个依赖属性: Value
, MaximumValue
和MinimumValue
。 我使用强制来确保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中被强制是好的,但我也希望被强制的值也能通过数据绑定。
哇,这是令人惊讶的。 在依赖项属性上设置值时,绑定表达式会在值强制运行前更新!
如果您查看Reflector中的DependencyObject.SetValueCommon,则可以在该方法的中途看到对Expression.SetValue的调用。 在绑定已更新后,调用UpdateEffectiveValue来调用CoerceValueCallback的操作即将结束。
你也可以在框架类中看到它。 从新的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; }
如果拖动Slider然后单击该按钮,则会看到类似“值从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的非强制值更新绑定。
你强制v
是一个int类型,并且是一个这样的值类型。 因此它被存储在堆栈中。 它没有连接到baseValue
。 所以改变v不会改变baseValue。
相同的逻辑适用于baseValue。 它通过值传递(而不是通过引用),所以改变它不会改变实际参数。
v
被返回,并且明确用于更新UI。
您可能需要研究将属性数据类型更改为引用类型。 然后它会通过引用传递,并且所做的任何更改都会反射回来。 假定数据绑定过程不会创建副本。
链接地址: http://www.djcxy.com/p/67561.html上一篇: Why does my data binding see the real value instead of the coerced value?
下一篇: Libav (ffmpeg) copying decoded video timestamps to encoder