代码之家  ›  专栏  ›  技术社区  ›  jpsstavares

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

  •  3
  • jpsstavares  · 技术社区  · 14 年前

    我正在尝试使用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>
    

    下面是ViewModel的代码:

    class TestStepListingStepViewModel : ViewModelBase, IDataErrorInfo
    {
        private int _runAfter = 0;
        public int RunAfter
        {
            get
            {
                return _runAfter;
            }
    
            set
            {
                if (_runAfter != value)
                {
                    _runAfter = value;
                    OnPropertyChanged("RunAfter");
                }
            }
        }
    
    string IDataErrorInfo.Error
        {
            get { return null; }
        }
    
        string IDataErrorInfo.this[string columnName]
        {
            get
            {
                string message = null;
                if (columnName == "RunAfter")
                    message = validateRunAfter();
    
                return message;
            }
        }
    
        private string validateRunAfter()
        {
            if (_runAfter >= _order)
                return "Run After value must be less than its Step Order (#) value.";
    
            return null;
        }
    }
    

    我想搞清楚这两天怎么了!一个有一双新眼睛的人能想出办法吗?

    编辑: 以下是文本框处理程序的代码:

    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框架的3.5版。 我的应用程序非常复杂,所以我无法创建仅重新创建此部分的小项目。我希望你们中的一些人已经有了这个问题并且知道如何解决它。

    再次感谢大家!

    3 回复  |  直到 14 年前
        1
  •  6
  •   Iain Holder    14 年前

    是的,马特是对的。我希望我一小时前就看到他的答案,不要花时间自己去发现问题。

    另一个对我有效的选项是使用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");
            }
    
        2
  •  5
  •   Matt Casto    14 年前

    我相信问题出在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>
    

    您引用的是验证错误的零项,当validation.hasError为true时,该项很好。但是,当validation.haserror设置为false时,工具提示属性的绑定将无效。

    作为解决方法,您可以尝试在validation.hasError上创建另一个触发器,其值为false,将清除工具提示。

        3
  •  0
  •   Edd    9 年前

    您引用的是验证错误的零项,当validation.hasError为true时,该项很好。但是,当validation.haserror设置为false时,工具提示属性的绑定将无效。

    作为解决方法,您可以尝试在validation.hasError上创建另一个触发器,其值为false,将清除工具提示。

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