为啥我的数据绑定看到的是真实值而不是强制值?
Posted
技术标签:
【中文标题】为啥我的数据绑定看到的是真实值而不是强制值?【英文标题】:Why does my data binding see the real value instead of the coerced value?为什么我的数据绑定看到的是真实值而不是强制值? 【发布时间】:2011-03-20 09:40:59 【问题描述】:我正在编写一个真正的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 中强制它很好,但我希望强制值也能贯穿数据绑定。
【问题讨论】:
似乎绑定不支持考虑 val。你试过 TextBox 的 diff 模式吗? 【参考方案1】:哇,这太令人惊讶了。当您在依赖属性上设置值时,绑定表达式会在值强制运行之前更新!
如果你查看Reflector中的DependencyObject.SetValueCommon,你可以看到在方法中途对Expression.SetValue的调用。在绑定已经更新之后,将调用 CoerceValueCallback 的 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”的消息。
【讨论】:
有趣,所以这是预期的行为。我想我的下一步是弄清楚如何提供一个可绑定的ActualValue
属性,以反映NumericUpDown 中实际显示的内容。您是否知道比仅重新评估属性更改处理程序中的强制并在那里设置ActualValue
更好的方法?【参考方案2】:
旧问题的新答案::-)
在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 的非强制值更新绑定。
【讨论】:
旧答案的新问题:)。我有这个问题,但“GetBindingExpression”返回空值。当值被绑定时,我试图强制!它在用户设置值时起作用。所以一定有一些上下文问题(没有完全加载或其他)?有什么指点吗? 如果您尝试在某个时候执行Value = x
,那么它将清除绑定。请改用SetCurrentValue
。【参考方案3】:
您正在强制 v
这是一个 int 并且是这样的值类型。因此,它存储在堆栈中。它与baseValue
没有任何关系。所以改变 v 不会改变 baseValue。
同样的逻辑也适用于 baseValue。它是按值传递(不是按引用),因此更改它不会更改实际参数。
v
被返回,显然用于更新 UI。
您可能想研究将属性数据类型更改为引用类型。然后它将通过引用传递,所做的任何更改都将反映回源。假设数据绑定过程不创建副本。
【讨论】:
如果从强制方法返回为 Object 不会被装箱吗?以上是关于为啥我的数据绑定看到的是真实值而不是强制值?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 SQL 服务器在我的表中插入 0 值而不是使用函数的正确值