WPF検証エラーの検出


115

WPFでは、ExceptionValidationRuleまたはを使用して、データバインディング中にデータレイヤーでスローされたエラーに基づいて検証をセットアップできます。DataErrorValidationRule

この方法で多数のコントロールを設定し、[保存]ボタンがあるとします。ユーザーが[保存]ボタンをクリックした場合、保存を続行する前に検証エラーがないことを確認する必要があります。検証エラーがある場合は、それらを非難する必要があります。

WPFで、Data Boundコントロールに検証エラーが設定されているかどうかをどのように確認しますか?

回答:


137

この投稿は非常に役に立ちました。貢献してくれたすべての人に感謝します。ここにあなたが好きか嫌いかの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);
}

1
私はこの特定のソリューションがとても好きです!
ChristopheD、

このスレッドにつまずいただけです。非常に便利な小さな機能。ありがとう!
Olav Haugen、2011年

特定のDataContextにバインドされたDependencyObjectのみを列挙する方法はありますか?私はツリーウォークの考えが好きではありません。特定のデータソースにリンクされたバインディングのコレクションがある場合があります。
ZAB 2014

5
ただ疑問に思って、IsValid関数をどのように呼び出しますか?CanExecute[保存]ボタンのコマンドに関連すると思われる設定を行ったようです。コマンドを使用しない場合、これは機能しますか?そして、ボタンは、チェックする必要がある他のコントロールにどのように関連していますか?これを使用する方法について私の唯一の考えは、IsValid検証する必要がある各コントロールを呼び出すことです。編集:sender私が保存ボタンとして期待して いるものを検証しているようです。それは私には正しくないようです。
Nicholas Miller

1
@Nick Miller a Windowも依存オブジェクトです。私はおそらく、何らかのイベントハンドラをに設定していますWindow。または、クラスIsValid(this)から直接呼び出すこともできますWindow
akousmata

47

次のコード(Chris Sell&Ian GriffithsによるProgramming WPFブックから)は、依存関係オブジェクトとその子のすべてのバインディングルールを検証します。

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

}

あなたはあなたのページ/ウィンドウでこのような保存ボタンクリックイベントハンドラでこれを呼び出すことができます

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

    ....
   }
}

33

投稿されたコードは、ListBoxを使用すると機能しませんでした。私はそれを書き直し、今それは動作します:

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

1
私のItemsControlで作業するためのソリューションに賛成票を投じてください。
ジェフT.

1
このソリューションを使用して、データグリッドに検証エラーがあるかどうかを確認しています。ただし、このメソッドは私のviewmodelコマンドのcanexecuteメソッドで呼び出され、ビジュアルツリーオブジェクトへのアクセスはMVVMパターンに違反していると思いませんか。代替案はありますか?
Igor Kondrasovas

16

同じ問題があり、提供された解決策を試しました。H-Man2とskiba_kのソリューションの組み合わせは、1つの例外を除いて、ほとんど問題なく動作しました。MyWindowにはTabControlがあります。また、検証ルールは、現在表示されているTabItemに対してのみ評価されます。そこで、VisualTreeHelperをLogicalTreeHelperに置き換えました。今では動作します。

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

7

Deanの優れたLINQ実装に加えて、コードをDependencyObjectsの拡張機能にラップするのを楽しみました:

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

これにより、再利用性を考えると非常に便利です。


2

小さな最適化を提供します。

同じコントロールに対してこれを何度も行う場合は、上記のコードを追加して、実際に検証ルールがあるコントロールのリストを保持できます。次に、有効性をチェックする必要がある場合は、ビジュアルツリー全体ではなく、それらのコントロールのみを調べます。このようなコントロールが多数ある場合、これははるかに優れていることがわかります。


2

これは、WPFでのフォーム検証用のライブラリです。Nugetパッケージはこちら

サンプル:

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

アイデアは、追跡する入力コントロールを伝える添付プロパティを介して検証スコープを定義することです。その後、次のことができます。

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

0

すべてのコントロールツリーを再帰的に反復処理して、添付されているプロパティValidation.HasErrorPropertyを確認し、その中で見つかった最初のプロパティに焦点を合わせることができます。

また、すでに記述されている多くのソリューションを使用することもできます。このスレッドで例と詳細情報を確認できます。



0

回答フォームaoganでは、検証ルールを明示的に繰り返すのではなく、呼び出すだけ 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;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.