2010-06-21 37 views
3

我想使用IDataErrorInfo验证我的MVVM应用程序中的数据,但我遇到了一些问题。使用MVVM实现验证IDataErrorInfo的数据异常

当我设置我的文本框与无效值,验证工作正常。但经过我设置TextBox为有效值的价值,我得到这个异常:

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> 

这里是代码视图模型:

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; 
    } 
} 

我想弄清楚这两天有什么问题!可以用一双清新的眼睛看出来吗?

编辑: 这里是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版本。 我的应用程序非常复杂,所以我不能创建一个仅重建此部分的小项目。我的希望是,你们中的一些人已经有这个问题,并知道如何解决它。

再次感谢大家!

+0

你可以在你的文本框(TextBoxLostFocus,TextBoxPreviewKeyDown,TextBoxPreviewTextInput)事件处理程序中发布代码吗?我试着运行没有这些处理程序的代码,它对我来说工作得很好。 你还使用什么.net版本? – Andrii 2010-06-21 19:22:45

+0

添加了您要求的信息。感谢您的帮助 – jpsstavares 2010-06-22 11:03:08

回答

6

是的,马特是对的。我希望我在小时前看过他的回答,不要花时间自行寻找问题。

对我而言,其他选项是使用转换器类来检查错误列表是否包含项目。所以它看起来像

<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"); 
     } 
+0

好吧,马特解决方案似乎不起作用(请参阅我对他的回答的回复),但您的转换器确实有效。当然,我更喜欢像他这样的解决方案,没有代码需要... – jpsstavares 2010-06-23 09:02:40

+0

只有一个旁注:使用* Path = Validation.Errors *而不是* Path =(Validation.Errors)*并且代码不工作“静静地“(只有一个不太理解的调试消息发送到Visual Studio输出窗口)。 有人想知道为什么需要括号吗?在其他绑定(即标准MVVM代码中的矿井)中,不需要parentherys,就像Text = {Binding MyProp.MyMember} – 2011-09-27 15:35:05

+0

@D_Guidi:我认为这是因为Validation不是一个属性,并且错误是它的子属性,但Validation是类定义附加的属性错误。圆括号用于使点分隔的字符串成为单个名称。 – ygoe 2014-02-26 13:08:20

5

我相信问题出在您的TextBox模板的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时,您的ToolTip属性的绑定将变为无效。

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

+0

您的评估似乎是正确的,但即使在Validation.HasError = false上添加触发器后,我仍然会得到异常。 但我已经把这个例外多了一点。我在属性setter和验证方法上添加了一个断点,当我点击Backspace并且文本框变空时,异常会上升。 setter不被调用,并且当验证断点达到异常时已经抛出。感谢您的帮助 – jpsstavares 2010-06-23 08:54:53

0

你引用的项目是罚款时,Validation.HasError是真正的验证错误为零。但是,当Validation.HasError被设置为False时,您的ToolTip属性的绑定将变为无效。

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

此解决方案适用于我。感谢您的描述和您的帮助!