在UserControl中显示验证错误

时间:2010-11-12 19:44:23

标签: c# silverlight validation xaml

我不确定为什么验证状态不会反映在我的用户控件中。 我抛出一个异常,但由于某种原因,控件没有显示验证状态......当我在我的MainPage上使用标准Textbox(现在在我的例子中注释掉)时,它显示错误状态,不知道为什么它不包裹它。

我已经缩减了这一点,所以基本上它是一个包裹TextBox的用户控件。 我错过了什么?

MyUserControl XAML:

<UserControl x:Class="ValidationWithUserControl.MyUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <TextBox x:Name="TextBox"/>
    </Grid>
</UserControl>

MyUserControl代码背后:

public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(MyUserControl_Loaded);
        this.TextBox.Unloaded += new RoutedEventHandler(TextBox_Unloaded);
    }

    public string Value
    {
        get { return (string)base.GetValue(ValueProperty); }
        set { base.SetValue(ValueProperty, value); }
    }

    public static DependencyProperty ValueProperty =
        DependencyProperty.Register(
        "Value",
        typeof(string),
        typeof(MyUserControl),
        new PropertyMetadata(null));

    private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
    {
        this.TextBox.SetBinding(TextBox.TextProperty, new Binding()
        {
            Source = this,
            Path = new PropertyPath("Value"),
            Mode = BindingMode.TwoWay,
            ValidatesOnExceptions = true,
            NotifyOnValidationError= true
        });  
    }

    private void TextBox_Unloaded(object sender, RoutedEventArgs e)
    {
        this.TextBox.ClearValue(TextBox.TextProperty);
    }
}

我的主页XAML:

<Grid x:Name="LayoutRoot" Background="LightBlue">
    <StackPanel>
        <uc:MyUserControl x:Name="UC" Value="{Binding Path=Value, Mode=TwoWay}" Height="20" Width="100" />
        <!--TextBox x:Name="MS" Text="{Binding Path=Value, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}" Height="20" Width="100" /-->
    </StackPanel>
</Grid>

我的主页代码背后:

public partial class MainPage : UserControl
{
    private Model model;
    //private Model model2;

    public MainPage()
    {
        InitializeComponent();
        this.model = new Model("UC");
        //this.model2 = new Model("MS");
        this.UC.DataContext = this.model;
        //this.MS.DataContext = this.model2;
    }
}

我的模特:

public class Model
{
    public Model(string answer)
    {
        this.answer = answer;
    }

    private string answer;
    public string Value
    {
        get
        {
            return this.answer;
        }
        set
        {
            if (!String.IsNullOrEmpty(value))
                this.answer = value;
            else
                throw new Exception("Error");
        }
    }
}

4 个答案:

答案 0 :(得分:8)

好的,我终于想出了如何处理这个问题。

您需要做的是从原始绑定中复制验证并将其发送到Textbox绑定。

要实现这一目标,首先要做的是在用户控件中实现 INotifyDataErrorInfo 接口。然后,您必须验证用户控件以获取GetErrors函数的确切验证文本(可以使用 Validation.GetErrors 完成此操作。)

这是一个基本的实现,它在VB中,但我相信你明白了。

    Public Event ErrorsChanged(ByVal sender As Object, ByVal e As System.ComponentModel.DataErrorsChangedEventArgs) Implements System.ComponentModel.INotifyDataErrorInfo.ErrorsChanged

Public Function GetErrors(ByVal propertyName As String) As System.Collections.IEnumerable Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
    Dim returnValue As System.Collections.IEnumerable = Nothing

    Dim errorMessage As String = Nothing


    If propertyName = "Value" Then

        If Validation.GetErrors(Me).Count = 0 Then
            errorMessage = ""
        Else
            errorMessage = Validation.GetErrors(Me).First.ErrorContent.ToString
        End If

        If String.IsNullOrEmpty(errorMessage) Then
            returnValue = Nothing
        Else
            returnValue = New List(Of String)() From {errorMessage}
        End If

    End If

    Return returnValue

End Function

Public ReadOnly Property HasErrors As Boolean Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
    Get
        Return Validation.GetErrors(Me).Any()
    End Get
End Property

接下来要做的就是通知你控制它变得无效。您必须在2个地方执行此操作。

第一个将出现在 BindingValidationError事件上。第二个将在 Value PropertyChangedCallback函数中(必须在注册DependencyProperty时指定)

Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(XDateTimePicker), New PropertyMetadata(Nothing, AddressOf ValuePropertyChangedCallback))

Public Shared Sub ValuePropertyChangedCallback(ByVal dependencyObject As DependencyObject, ByVal dependencyPropertyChangedEventArgs As DependencyPropertyChangedEventArgs)
    DirectCast(dependencyObject, MyUserControl).NotifyErrorsChanged("Value")
End Sub

Private Sub MyUserControl_BindingValidationError(ByVal sender As Object, ByVal e As System.Windows.Controls.ValidationErrorEventArgs) Handles Me.BindingValidationError
    Me.NotifyErrorsChanged("Value")
End Sub

Public Sub NotifyErrorsChanged(ByVal propertyName As String)
    RaiseEvent ErrorsChanged(Me, New System.ComponentModel.DataErrorsChangedEventArgs(propertyName))
End Sub

现在大部分工作都已完成,但您仍需要对绑定进行一些调整。

创建TextBox绑定时,需要将 NotifyOnValidationError 设置为False,以避免原始绑定和Textbox绑定之间出现通知循环。 ValidatesOnExceptions ValidatesOnDataErrors ValidatesOnNotifyDataErrors 需要设置为True。

        Dim binding As New System.Windows.Data.Binding

    binding.Source = Me
    binding.Path = New System.Windows.PropertyPath("Value")
    binding.Mode = Data.BindingMode.TwoWay
    binding.NotifyOnValidationError = False 
    binding.ValidatesOnExceptions = True
    binding.ValidatesOnDataErrors = True
    binding.ValidatesOnNotifyDataErrors = True

    Me.TextBox1.SetBinding(TextBox.TextProperty, binding)

最后,您需要在XAML中将 NotifyOnValidationError ValidatesOnNotifyDataErrors 属性设置为True。

<uc:MyUserControl x:Name="UC" Value="{Binding Path=Value, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnNotifyDataErrors=True}" Height="20" Width="100" />

答案 1 :(得分:3)

此行为是由添加的绑定级别引起的。 绑定不支持转发验证错误。

幕后发生的事情:

  1. 用户将文本输入TextBox,MyUserControl_Loaded中定义的绑定将值传递给MyUserControl.ValueProperty。
  2. 接下来,在MainPage XAML for MyUserControl中定义的绑定将值传递给Model。
  3. Model.Value.set()中抛出的异常由MainPage XAML中设置的绑定处理。
  4. 没有异常传递给与TextBox关联的绑定。
  5. 由于UserControl没有将ValidatesOnExceptions设置为true,因此不会显示任何可视指示。
  6. 要解决此问题,您可以将文本框直接绑定到模型,如下所示:

    this.TextBox.SetBinding(TextBox.TextProperty, new Binding()
    {
        Source = this.DataContext, // bind to the originating source
        Path = new PropertyPath("Value"),
        Mode = BindingMode.TwoWay,
        ValidatesOnExceptions = true,
        NotifyOnValidationError= true
    });  
    

    自6个月过去以后,我想知道你是否以及如何克服这个问题。

答案 2 :(得分:3)

您应该能够将依赖项属性绑定直接回显到usercontrol文本框。这将拾取验证错误,方法与父视图中的绑定相同。在您的MyUserControl_Loaded函数中:

var valueBinding = BindingOperations.GetBindingBase(this, ValueProperty);
if (valueBinding != null) TextBox.SetBinding(TextBox.TextProperty, valueBinding);

答案 3 :(得分:1)

如果有人来寻找一个好的(阅读:“不是用VBA编写并完成”)这个问题的解决方案,我写了一个base class(虽然我只用无形控件测试它,不要根据@The_Black_Smurf在C#中的回答知道它是否适用于UserControl s:

namespace MyApplication.Controls
{
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;

    public abstract class ControlBaseWithValidation : Control, INotifyDataErrorInfo
    {
        public ControlBaseWithValidation()
        {
            // remove the red border that wraps the whole control by default
            Validation.SetErrorTemplate(this, null);
        }

        public delegate void ErrorsChangedEventHandler(object sender, DataErrorsChangedEventArgs e);

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public bool HasErrors
        {
            get
            {
                var validationErrors = Validation.GetErrors(this);
                return validationErrors.Any();
            }
        }

        public IEnumerable GetErrors(string propertyName)
        {
            var validationErrors = Validation.GetErrors(this);
            var specificValidationErrors =
                validationErrors.Where(
                    error => ((BindingExpression)error.BindingInError).TargetProperty.Name == propertyName).ToList();
            var specificValidationErrorMessages = specificValidationErrors.Select(valError => valError.ErrorContent);
            return specificValidationErrorMessages;
        }

        public void NotifyErrorsChanged(string propertyName)
        {
            if (ErrorsChanged != null)
            {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        protected static void ValidatePropertyWhenChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            ((ControlBaseWithValidation)dependencyObject).NotifyErrorsChanged(dependencyPropertyChangedEventArgs.Property.Name);
        }
    }
}

使用ControlBaseWithValidation类作为您的无外观控件的基类而不是Control类,并在您想要的任何依赖项属性上添加ValidatePropertyWhenChangedCallback回调作为PropertyChangedCallback验证。