I'm trying to validate data in my MVVM application using IDataErrorInfo, but I'm running into some problems.
When I set my TextBox with an invalid value, the validation works fine. But after I set the value of the TextBox to a valid value and I get this exception:
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)'
Here is the code for the view:
<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>
And here is the Code for the 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;
}
}
I'm trying to figure out what's wrong with this for two days! Can some one with a pair of fresh eyes figure it out?
EDIT: Here is the code of the TextBoxs handlers:
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);
}
}
Also, I'm using version 3.5 of the .Net Framework. My application is very complex so I won't be able to create a small project that recreates only this part. My hope is that some of you already had this problem and know how to solve it.
Thanks again everyone!