【问题标题】:WPF with MVVM and DataAnnotations, Validation Errors in a UserControl带有 MVVM 和 DataAnnotations 的 WPF,用户控件中的验证错误
【发布时间】:2013-10-13 11:46:34
【问题描述】:

我有一个将在我们正在开发的应用程序中重用的 UserControl。 我们正在使用基于 MVVMLight 的框架。

为了简单起见,假设用户控件仅包含一个文本框并公开一个名为“数量”的依赖项属性。用户控件上的文本框数据绑定到依赖属性“数量”。

当在视图上使用用户控件时,用户控件的“数量”依赖属性将数据绑定到 ViewModel 中的属性。 (这个 ViewModel 是我们通过 MVVMLight ViewModelLocator 的视图的数据上下文)。

这一切都很好!绑定工作,属性设置如我所料。在验证之前一切都很好。

我们正在使用 DataAnnotations 来装饰我们的 ViewModel 属性。 ViewModel 包含 INotifyDataErrorInfo 的自定义实现。我们为大多数输入控件实现了自定义样式,以在控件周围显示红色边框,并在控件旁边显示验证错误消息。所有这些在正常情况下都非常有效(例如,视图上的文本框绑定到视图模型中的属性)。

当我使用此用户控件尝试相同的方法时,我最终得到的是整个用户控件周围的红色边框,并且实际文本框上没有错误指示。似乎存在错误的事实反映在 UI 中,但它只是没有进入我想要的控件。

我已经在 stackoverflow 上搜索过这个问题,在那些有解决方案的问题中,似乎没有一个适合我的情况。

我的第一个猜测是,因为实际的文本框直接绑定到依赖属性本身而不是我的视图模型上的属性,所以它没有被正确地通知生成的错误。有没有办法通过用户控件传播视图模型中生成的错误,然后传播到文本框?

您能提供的任何帮助或建议都非常好,谢谢。

这是 UserControl xaml。

<UserControl x:Class="SampleProject.UserControls.SampleControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d"  x:Name="sampleControl"
         d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=sampleControl}">
        <TextBox Text="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Width="100" Height="30" />
</Grid>
</UserControl>

后面的 UserControl 代码。

public partial class SampleControl : UserControl
{
    public SampleControl()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty QuantityProperty = 
         DependencyProperty.Register("Quantity", typeof(int?), typeof(SampleControl), 
    new FrameworkPropertyMetadata{DefaultValue=null, BindsTwoWayByDefault = true});

    public int? Quantity
    {
        get { return (int?)GetValue(QuantityProperty); }
        set { SetValue(QuantityProperty, value); }
    }
}

用于视图。

<userControls:SampleControl Grid.Row="1" Quantity="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Height="60" Width="300"/>

ViewModel 属性。

[Required(ErrorMessage = "Is Required")]
[Range(5, 10, ErrorMessage = "Must be greater than 5")]
public int? Quantity
{
    get { return _quantity; }
    set { Set(() => Quantity, ref _quantity, value); }
}
private int? _quantity;

(*注意,setter 中的 Set 方法只是基本视图模型中的辅助方法,用于设置支持属性并为其引发 PropertyChanged 事件。)

【问题讨论】:

  • 代码运行时,错误信息出现在同一个TextBox?
  • 是的。如果该文本框位于视图本身并直接绑定到 ViewModel 上的 Quantity 属性,则验证错误会出现在文本框上。但是当文本框在用户控件中并且绑定通过用户控件的依赖属性时,验证错误就会丢失。
  • 即使你放了一个断点?
  • 我不确定你想让我在哪里设置断点...但是我已经验证了视图模型上的属性正在设置并且正在生成验证错误。
  • 你找到解决这个问题的方法了吗?我也遇到了类似的问题

标签: c# wpf validation mvvm


【解决方案1】:

尝试从UserControl 中删除DataContext。而不是设置,Bind 直接从 TextBox 使用 RelativeSource Binding 到实际属性:

<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor, 
    AncestorType={x:Type YourControlNamespace:SampleControl, 
    ValidatesOnDataErrors=True}}}" Width="100" Height="30" />

更新>>>

如果不这样做,只要绑定到这个属性的视图模型总是有一个同名的属性可以绑定,你可以得到这个Binding来搜索父母的DataContexts 这样的:

<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor, 
    AncestorLevel=2, ValidatesOnDataErrors=True}}}" Width="100" Height="30" />

您需要将2 更改为TextBox 具有的正确数量的父元素,然后才能访问具有正确属性的控件。例如,使用2 级别意味着框架将尝试在TextBoxs 父级的父控件的DataContext 中查找名为QuantityBind 的属性。 使用 AncestorLevel 来处理这个问题会比较棘手,因为我相信像 Grids 这样的“隐藏”元素不包含在父元素中。

【讨论】:

  • 不,这不能解决任何问题。该行为与在 LayoutRoot 上设置的 datacontext 以及不使用 RelativeSource 绑定的绑定完全相同。
  • 感谢您的建议,虽然我可以在不使用相对源绑定的情况下通过根本不在用户控件上指定数据上下文来实现您上面的功能,因此它将继承父数据上下文(这是我的视图模型)。那么控件上的绑定就必须是'Text="{Binding Quantity}"'。但是,我不希望强制此控件的用户必须以某种方式命名其视图模型上的属性,以便他们可以使用 UI 组件。这 (goo.gl/zR2zN6) 是一种用于创建避免该问题的用户控件的模式。
  • 好吧,那么您将不得不以某种方式添加另一个 DependencyProperty,您可以在其中将数据注释注入到 Value 字段中......这已经开始过度设计,因为它应该是一个有用的东西.
【解决方案2】:

您需要将用户控件上设置的绑定拾取并将它们放在控件上,无需将用户控件绑定到它自己的DataContext。 这可以在加载用户控件后完成。

为防止用户控件出现红色边框,请移除默认错误模板:

Validation.ErrorTemplate="{x:Null}"

示例用户控件 XAML:

UserControl x:Class="DxUserControlValidation.MyUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         Validation.ErrorTemplate="{x:Null}"
         d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Vertical">
    <TextBlock Text="Value 1:" Margin="2"/>
    <TextBox Name="txtBox1" Margin="2"/>
    <TextBlock Text="Value 2:" Margin="2"/>
    <TextBox Name="txtBox2" Margin="2"/>
</StackPanel>

public partial class MyUserControl : UserControl
{
    public static readonly DependencyProperty Value1Property;
    public static readonly DependencyProperty Value2Property;

    static MyUserControl()
    {
        Value1Property = DependencyProperty.Register("Value1", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
        Value2Property = DependencyProperty.Register("Value2", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
    }

    public MyUserControl()
    {
        InitializeComponent();

        Loaded += (s, e) =>
        { 
            Binding value1Binding = BindingOperations.GetBinding(this, Value1Property);
            if (value1Binding != null) txtBox1.SetBinding(TextBox.TextProperty, value1Binding);
            Binding value2Binding = BindingOperations.GetBinding(this, Value2Property);
            if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
        };
    }

    public string Value1
    {
        get { return (string)GetValue(Value1Property); }
        set { SetValue(Value1Property, value); }
    }

    public string Value2
    {
        get { return (string)GetValue(Value2Property); }
        set { SetValue(Value2Property, value); }
    }
}

如果没有绑定,则直接将值赋给控件:

if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
else txtBox2.Text = Value2;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 2011-03-21
    • 1970-01-01
    • 2016-08-08
    • 2010-12-17
    相关资源
    最近更新 更多