WPF与MVVM和DataAnnotations,一个UserControl中的验证错误

7
我有一个UserControl,将在我们正在开发的应用程序中重复使用。我们使用基于MVVMLight的框架。
为了简单起见,假设用户控件仅包含一个文本框并公开一个名为“Quantity”的依赖属性。用户控件上的文本框与依赖属性“Quantity”进行数据绑定。
当用户控件在视图上使用时,用户控件的“Quantity”依赖属性会与ViewModel中的属性进行数据绑定。(通过MVVMLight ViewModelLocator,此ViewModel是视图的数据上下文)。
这一切都很好!绑定有效,属性设置正常。直到涉及验证时才出现问题。
我们使用DataAnnotations来装饰ViewModel属性。ViewModel包含INotifyDataErrorInfo的自定义实现。我们已经为大多数输入控件实现了自定义样式,以显示控件周围的红色边框和消息,其中显示验证错误消息。所有这些在正常情况下(例如,View上的Textbox绑定到ViewModel中的属性)都非常好用。
当我尝试使用此用户控件采用相同的方法时,我得到的是整个用户控件周围的红色边框和实际文本框上没有错误指示。似乎UI反映了存在错误的事实,但它只是没有传递到我想要的控件上。
我在stackoverflow上搜索了这个问题,但在那些有解决方案的问题中,没有一个适用于我的情况。
我的第一个猜测是,由于实际文本框直接绑定到依赖属性本身而不是绑定到视图模型上的属性,因此它无法正确地通知生成的错误。是否有一种方法可以通过用户控件将在ViewModel中生成的这些错误传播到文本框?
非常感谢您提供的任何帮助或建议。
以下是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>

用户控件的代码后台。
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"/>

视图模型属性。
[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事件。)


代码运行时,错误消息会出现在同一个文本框中吗? - trinaldi
是的。如果该文本框直接位于视图上,并直接绑定到ViewModel上的数量属性,则验证错误会出现在文本框上。但当文本框位于用户控件内部,并且绑定通过用户控件的依赖属性进行时,验证错误就会丢失。 - thornhill
即使您设置了断点? - trinaldi
我不确定您想让我在哪里设置断点......但我已经验证了视图模型上的属性被设置并且生成了验证错误。 - thornhill
你找到这个问题的解决方案了吗?我也遇到了类似的问题。 - Anup Sharma
2个回答

0

尝试从UserControl中移除DataContext。不要设置它,而是使用RelativeSource Binding直接从TextBox绑定到实际属性:

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

更新 >>>

如果没有成功,只要与此属性绑定的视图模型始终具有与之绑定的相同名称的属性,您可以通过以下方式使此 Binding 在父级的 DataContext 中进行搜索:

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

您需要更改2,以便成为在访问正确属性的控件之前TextBox具有的父元素的正确数量。例如,使用级别2意味着框架将尝试在TextBox的父级父级控件的DataContext中查找名为Quantity的属性进行绑定。然而,使用AncestorLevel会更加棘手,因为我认为像Grid这样的“隐藏”元素不包括在父级中。


不,那并没有解决任何问题。行为与在LayoutRoot上设置datacontext并且绑定未使用RelativeSource绑定时完全相同。 - thornhill
谢谢您的建议,这个方法能够达到效果。但是我可以通过不在用户控件上指定数据上下文而实现您以上所述的功能,使其继承父级数据上下文(即我的视图模型)。 然后控件上的绑定只需为'Text ="{Binding Quantity}"'。 但是,我更喜欢不强制要求此控件的用户按特定方式命名其视图模型上的属性,以便他们可以使用UI组件。 这是一种创建用户控件避免此问题的模式(http://goo.gl/zR2zN6)。 - thornhill
好的,那么你可能需要添加另一个 DependencyProperty,在其中可以将数据注释注入到 Value 字段中... 这开始变得过于工程化,而这本应是一项有帮助的任务。 - Kevin Cook

0

您需要获取用户控件上设置的绑定,并将它们放置在控件上,无需将用户控件绑定到其自身的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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接