programing tip

WPF 유효성 검사 오류 감지

itbloger 2020. 7. 23. 19:06
반응형

WPF 유효성 검사 오류 감지


WPF에서는 ExceptionValidationRule또는을 사용하여 데이터 바인딩 중 데이터 계층에 발생한 오류를 기반으로 유효성 검사를 설정할 수 있습니다 DataErrorValidationRule.

이 방법으로 많은 컨트롤을 설정했고 저장 버튼이 있다고 가정하십시오. 사용자가 저장 버튼을 클릭하면 저장을 진행하기 전에 유효성 검사 오류가 없는지 확인해야합니다. 유효성 검사 오류가 있으면 오류를 해결하려고합니다.

WPF에서 데이터 바운드 컨트롤에 유효성 검사 오류가 설정되어 있는지 어떻게 알 수 있습니까?


이 게시물은 매우 도움이되었습니다. 기여한 모든 사람에게 감사합니다. 여기 당신이 좋아하거나 싫어할 LINQ 버전이 있습니다.

private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = IsValid(sender as DependencyObject);
}

private bool IsValid(DependencyObject obj)
{
    // The dependency object is valid if it has no errors and all
    // of its children (that are dependency objects) are error-free.
    return !Validation.GetHasError(obj) &&
    LogicalTreeHelper.GetChildren(obj)
    .OfType<DependencyObject>()
    .All(IsValid);
}

다음 코드 (Chris Sell & Ian Griffiths의 Programming WPF book에서 제공)는 종속성 개체와 해당 자식에 대한 모든 바인딩 규칙의 유효성을 검사합니다.

public static class Validator
{

    public static bool IsValid(DependencyObject parent)
    {
        // Validate all the bindings on the parent
        bool valid = true;
        LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
        while (localValues.MoveNext())
        {
            LocalValueEntry entry = localValues.Current;
            if (BindingOperations.IsDataBound(parent, entry.Property))
            {
                Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                foreach (ValidationRule rule in binding.ValidationRules)
                {
                    ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
                    if (!result.IsValid)
                    {
                        BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
                        System.Windows.Controls.Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
                        valid = false;
                    }
                }
            }
        }

        // Validate all the bindings on the children
        for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
        {
            DependencyObject child = VisualTreeHelper.GetChild(parent, i);
            if (!IsValid(child)) { valid = false; }
        }

        return valid;
    }

}

You can call this in your save button click event handler like this in your page/window

private void saveButton_Click(object sender, RoutedEventArgs e)
{

  if (Validator.IsValid(this)) // is valid
   {

    ....
   }
}

The posted code did not work for me when using a ListBox. I rewrote it and now it works:

public static bool IsValid(DependencyObject parent)
{
    if (Validation.GetHasError(parent))
        return false;

    // Validate all the bindings on the children
    for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        if (!IsValid(child)) { return false; }
    }

    return true;
}

Had the same problem and tried the provided solutions. A combination of H-Man2's and skiba_k's solutions worked almost fine for me, for one exception: My Window has a TabControl. And the validation rules only get evaluated for the TabItem that is currently visible. So I replaced VisualTreeHelper by LogicalTreeHelper. Now it works.

    public static bool IsValid(DependencyObject parent)
    {
        // Validate all the bindings on the parent
        bool valid = true;
        LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
        while (localValues.MoveNext())
        {
            LocalValueEntry entry = localValues.Current;
            if (BindingOperations.IsDataBound(parent, entry.Property))
            {
                Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                if (binding.ValidationRules.Count > 0)
                {
                    BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
                    expression.UpdateSource();

                    if (expression.HasError)
                    {
                        valid = false;
                    }
                }
            }
        }

        // Validate all the bindings on the children
        System.Collections.IEnumerable children = LogicalTreeHelper.GetChildren(parent);
        foreach (object obj in children)
        {
            if (obj is DependencyObject)
            {
                DependencyObject child = (DependencyObject)obj;
                if (!IsValid(child)) { valid = false; }
            }
        }
        return valid;
    }

In addition to the great LINQ-implementation of Dean, I had fun wrapping the code into an extension for DependencyObjects:

public static bool IsValid(this DependencyObject instance)
{
   // Validate recursivly
   return !Validation.GetHasError(instance) &&  LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
}

This makes it extremely nice considering reuseablity.


I would offer a small optimization.

If you do this many times over the same controls, you can add the above code to keep a list of controls that actually have validation rules. Then whenever you need to check for validity, only go over those controls, instead of the whole visual tree. This would prove to be much better if you have many such controls.


Here is a library for form validation in WPF. Nuget package here.

Sample:

<Border BorderBrush="{Binding Path=(validationScope:Scope.HasErrors),
                              Converter={local:BoolToBrushConverter},
                              ElementName=Form}"
        BorderThickness="1">
    <StackPanel x:Name="Form" validationScope:Scope.ForInputTypes="{x:Static validationScope:InputTypeCollection.Default}">
        <TextBox Text="{Binding SomeProperty}" />
        <TextBox Text="{Binding SomeOtherProperty}" />
    </StackPanel>
</Border>

The idea is that we define a validation scope via the attached property telling it what input controls to track. Then we can do:

<ItemsControl ItemsSource="{Binding Path=(validationScope:Scope.Errors),
                                    ElementName=Form}">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type ValidationError}">
            <TextBlock Foreground="Red"
                       Text="{Binding ErrorContent}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

You can iterate over all your controls tree recursively and check the attached property Validation.HasErrorProperty, then focus on the first one you find in it.

you can also use many already-written solutions you can check this thread for an example and more information


You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It shows how to use validation in WPF and how to control the Save button when validation errors exists.


In answer form aogan, instead of explicitly iterate through validation rules, better just invoke expression.UpdateSource():

if (BindingOperations.IsDataBound(parent, entry.Property))
{
    Binding binding = BindingOperations.GetBinding(parent, entry.Property);
    if (binding.ValidationRules.Count > 0)
    {
        BindingExpression expression 
            = BindingOperations.GetBindingExpression(parent, entry.Property);
        expression.UpdateSource();

        if (expression.HasError) valid = false;
    }
}

참고URL : https://stackoverflow.com/questions/127477/detecting-wpf-validation-errors

반응형