Wpf 使用MVVM实现使用IDataErrorInfo验证数据时出现异常

Wpf 使用MVVM实现使用IDataErrorInfo验证数据时出现异常,wpf,validation,mvvm,binding,idataerrorinfo,Wpf,Validation,Mvvm,Binding,Idataerrorinfo,我试图使用IDataErrorInfo验证MVVM应用程序中的数据,但遇到了一些问题 当我将文本框设置为无效值时,验证工作正常。但在我将文本框的值设置为有效值并得到此异常后: A first chance exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll A first chance exception of type 'System.Reflection.TargetInvocatio

我试图使用IDataErrorInfo验证MVVM应用程序中的数据,但遇到了一些问题

当我将文本框设置为无效值时,验证工作正常。但在我将文本框的值设置为有效值并得到此异常后:

A first chance exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll
A first chance exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
System.Windows.Data Error: 16 : Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0).[0].ErrorContent; DataItem='TextBox' (Name='txtRunAfter'); target element is 'TextBox' (Name='txtRunAfter'); target property is 'ToolTip' (type 'Object') TargetInvocationException:'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
    at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
    at System.ThrowHelper.ThrowArgumentOutOfRangeException()
    at System.Collections.Generic.List`1.get_Item(Int32 index)
    at System.Collections.ObjectModel.Collection`1.get_Item(Int32 index)
    at System.Collections.ObjectModel.ReadOnlyCollection`1.get_Item(Int32 index)
    --- End of inner exception stack trace ---
    at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
    at MS.Internal.Data.PropertyPathWorker.GetValue(Object item, Int32 level)
    at MS.Internal.Data.PropertyPathWorker.RawValue(Int32 k)'
以下是视图的代码:

    <UserControl x:Class="Telbit.TeStudio.View.Controls.TestStepListingStepView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Background="{Binding BackgroundColor}">

    <UserControl.Resources>
        <Style x:Key="TestStepTextBox" TargetType="{x:Type TextBox}">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="HorizontalContentAlignment" Value="Left"/>
            <Setter Property="TextElement.FontSize" Value="10"/>
            <Setter Property="TextElement.FontWeight" Value="Regular"/>
            <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBox}">
                        <Border x:Name="Bd" SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                            <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter Property="BorderBrush" Value="#3d62a9"/>
                            </Trigger>
                            <Trigger Property="IsFocused" Value="true">
                                <Setter Property="BorderBrush" Value="#3d62a9"/>
                                <Setter Property="Background" Value="White"/>
                            </Trigger>
                            <Trigger Property="Validation.HasError" Value="true">
                                <Setter Property="ToolTip"
                                    Value="{Binding RelativeSource={RelativeSource Self}, 
                                    Path=(Validation.Errors)[0].ErrorContent}"/>
                                <Setter Property="Background" Value="#33FF342D"/>
                                <Setter Property="BorderBrush" Value="#AAFF342D"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>

    ...

    <TextBox Name="txtRunAfter" Grid.Column="4" Text="{Binding RunAfter, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TestStepTextBox}"
         LostFocus="TextBoxLostFocus" PreviewKeyDown="TextBoxPreviewKeyDown" PreviewTextInput="TextBoxPreviewTextInput"/>

    ...

 </UserControl>
两天来我一直在想这到底是怎么回事!有一双新眼睛的人能猜出来吗

编辑: 以下是TextBoxs处理程序的代码:

public partial class TestStepListingStepView : UserControl
{
    private string mInvalidCharPattern = "[^0-9]";

    public TestStepListingStepView()
    {
        InitializeComponent();

        DataObject.AddPastingHandler(this.txtRunAfter, new DataObjectPastingEventHandler(TextBoxPasting));
    }

    private void TextBoxLostFocus(object sender, RoutedEventArgs e)
    {
        TextBox txt = sender as TextBox;

        if (txt != null && string.IsNullOrEmpty(txt.Text))
            txt.Text = "0";
    }

    // Catch the space character, since it doesn't trigger PreviewTextInput
    private void TextBoxPreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Space) { e.Handled = true; }
    }

    // Do most validation here
    private void TextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        if (ValidateTextInput(e.Text) == false) { e.Handled = true; }
    }

    // Prevent pasting invalid characters
    private void TextBoxPasting(object sender, DataObjectPastingEventArgs e)
    {
        string lPastingText = e.DataObject.GetData(DataFormats.Text) as string;
        if (ValidateTextInput(lPastingText) == false) { e.CancelCommand(); }
    }

    // Do the validation in a separate function which can be reused
    private bool ValidateTextInput(string aTextInput)
    {
        if (aTextInput == null) { return false; }

        Match lInvalidMatch = Regex.Match(aTextInput, this.mInvalidCharPattern);
        return (lInvalidMatch.Success == false);
    }

}
此外,我正在使用.Net Framework的3.5版。 我的应用程序非常复杂,因此我无法创建一个仅重新创建此部分的小项目。我希望你们中的一些人已经有了这个问题,并且知道如何解决它


再次感谢大家

我认为问题在于Validation.HasError触发器中文本框的模板

<Trigger Property="Validation.HasError" Value="true">
    <Setter Property="ToolTip"
            Value="{Binding RelativeSource={RelativeSource Self}, 
            Path=(Validation.Errors)[0].ErrorContent}"/>
    <Setter Property="Background" Value="#33FF342D"/>
    <Setter Property="BorderBrush" Value="#AAFF342D"/>
</Trigger>

您正在引用验证错误的第0项,当validation.HasError为True时,这是正常的。但是,当Validation.HasError设置为False时,工具提示属性的绑定将无效


作为一种解决方法,您可以尝试在Validation.HasError上创建另一个触发器,其值为False,从而清除工具提示。

是的,Matt是对的。我希望我在一小时前看过他的答案,而不是花时间自己去寻找问题

另一个对我有用的选项是使用converter类来检查错误列表中是否有项。所以看起来像

<Trigger Property="Validation.HasError" Value="true"> 
<Setter Property="ToolTip" 
   Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource validationConverter},
   Path=(Validation.Errors)}"/> 

public class ValidationConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            ReadOnlyObservableCollection<ValidationError> errors = value as ReadOnlyObservableCollection<ValidationError>;
            if (errors == null) return value;
            if (errors.Count > 0)
            {
                return errors[0].ErrorContent;
            }
            return "";            
        }


        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException("This method should never be called");
        }

公共类ValidationConverter:IValueConverter
{
公共对象转换(对象值、类型targetType、对象参数、System.Globalization.CultureInfo区域性)
{
ReadOnlyObservableCollection errors=值为ReadOnlyObservableCollection;
if(errors==null)返回值;
如果(errors.Count>0)
{
返回错误[0]。错误内容;
}
返回“”;
}
公共对象转换回(对象值、类型targetType、对象参数、System.Globalization.CultureInfo区域性)
{
抛出新的NotImplementedException(“永远不应该调用此方法”);
}
您正在引用验证错误的第0项,当validation.HasError为True时,这是正常的。但是,当validation.HasError设置为False时,工具提示属性的绑定将无效

作为一种解决方法,您可以尝试在Validation.HasError上创建另一个值为False的触发器,该值将清除工具提示


这个解决方案对我很有效。感谢您的描述和帮助!

您能在文本框的事件处理程序中发布代码吗(TextBoxLostFocus、TextBoxPreviewKeyDown、textboxpreviewttemput)?我尝试运行您提供的代码,但没有这些处理程序,对我来说效果很好。另外,您使用的.net版本是什么?添加了您要求的信息。感谢您的帮助您的评估似乎是正确的,但即使在添加了验证触发器后,我仍然会收到异常。hasrerror=false。但我已稍微限制了异常le more。我在属性设置器和验证方法上添加了一个断点,当我点击Backspace时,异常会出现,文本框变为空。没有调用设置器,当验证断点达到时,异常已经抛出。感谢帮助,Matt解决方案似乎不起作用(请参阅我对他的回答的回答),但您的转换器确实工作了。当然,我更喜欢像他的解决方案,不需要任何代码隐藏…只需要一个旁注:使用Path=Validation.Errors而不是Path=(Validation.Errors),并且代码不能“静默”工作(只有发送到visual studio输出窗口的一条不太容易理解的调试消息)。有人想知道为什么需要括号吗?在其他绑定(即标准MVVM代码中的mine)中,不需要parentherys,例如Text={Binding MyProp.MyMember}@D_Guidi:我认为这是因为验证不是一个属性,而错误是它的子属性,但验证是定义附加属性错误的类。圆括号是使点分隔字符串成为单个名称所必需的。对我来说,真正的异常是确保中的
公共语言运行时异常
de>异常设置屏幕上有一个复选标记。
<Trigger Property="Validation.HasError" Value="true"> 
<Setter Property="ToolTip" 
   Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource validationConverter},
   Path=(Validation.Errors)}"/> 

public class ValidationConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            ReadOnlyObservableCollection<ValidationError> errors = value as ReadOnlyObservableCollection<ValidationError>;
            if (errors == null) return value;
            if (errors.Count > 0)
            {
                return errors[0].ErrorContent;
            }
            return "";            
        }


        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException("This method should never be called");
        }