2010-06-21 86 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,從而清除工具提示。

此解決方案適用於我。感謝您的描述和您的幫助!