Исключительная проверка данных с IDataErrorInfo с реализацией MVVM - PullRequest
3 голосов
/ 21 июня 2010

Я пытаюсь проверить данные в приложении MVVM с помощью IDataErrorInfo, но у меня возникают некоторые проблемы.

Когда я устанавливаю в TextBox недопустимое значение, проверка работает нормально. Но после того, как я установил значение 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;
    }
}

Я пытаюсь выяснить, что с этим не так два дня! Может кто-нибудь с парой свежих глаз понять это?

* * 1013 EDIT: Вот код обработчиков 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);
    }

}

Кроме того, я использую версию 3.5 .Net Framework. Мое приложение очень сложное, поэтому я не смогу создать небольшой проект, воссоздающий только эту часть. Я надеюсь, что некоторые из вас уже имели эту проблему и знают, как ее решить.

Еще раз спасибо всем!

Ответы [ 3 ]

7 голосов
/ 22 июня 2010

Да, Мэтт прав.Я бы хотел посмотреть его ответ час назад, чтобы не тратить время на поиск проблемы самостоятельно.

Другой вариант, который мне помог, - это использовать класс конвертера, который проверяет наличие элементов в списке ошибок.Так будет выглядеть

<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");
        }
5 голосов
/ 22 июня 2010

Я полагаю, что проблема связана с шаблоном вашего 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 голосов
/ 23 июня 2015

Вы ссылаетесь на нулевой элемент ошибок валидации, что нормально, когда Validation.HasError имеет значение True. Однако, когда для Validation.HasError установлено значение False, привязка свойства ToolTip становится недействительной.

В качестве обходного пути вы можете попробовать создать еще один триггер для Validation.HasError со значением False, которое очищает подсказку.

Это решение сработало для меня. Спасибо за ваше описание и вашу помощь!

...