6

C#/WPF/Entity Framework 4.0 アプリケーションでエレガントなフォーム検証を実装しようとしています。私は古典的なフォーム (いくつかのテキストボックスとチェックボックス) と保存ボタンを持っています。
ユーザーが保存を押したときにのみソース (およびデータベース) を更新し、ユーザーが保存を押したときにのみフォーム検証を実行したい。

パラメータを使用してすべてのバインディングを設定しましたUpdateSourceTrigger=Explicit。また、すべてのバインディングを 内に配置しましたBindingGroup

ユーザーが保存を押すUpdateSourcesと、バインディング グループのメソッドがトリガーされます。これは、バインディングUpdateSourceごとにトリガーされます。この時点 (ソースの更新が完了する前) で、フォームの検証が行われ、エラーが GUI で強調表示されるようにします。入力エラーがない場合、更新は自由に実行できます。

バインドされているフィールドに実装IDataErrorInfoし、すべてのバインディングにパラメーターを設定することで、これを達成できると思いました。EntityObjectValidatesOnDataErrors=True

残念ながら、ここで説明されているように、これは機能しませ: MSDN データバインディングの概要 - 「検証プロセス」という見出しの下のデータ検証

5) バインディング エンジンがソース プロパティを設定します。

6) ...... これは、ValidatesOnDataErrors が true に設定されているバインディングがチェックされるポイントです。

これは本当にばかげているように思えます。オブジェクトに既に「コミット」された後で、データを検証する必要があるのはなぜですか? 私は自分が望む動作を得る方法を何時間も探していました... 誰かが前にこのようなことをしたことがありますか?

したがって、主な質問は次のとおりです。
ソースが更新される前に入力を検証し、検証が失敗した場合に更新をキャンセルするにはどうすればよいですか?

4

1 に答える 1

2

IDataErrorInfo は propertyName のみを使用して特定のプロパティのエラーを取得するため、値をオブジェクトにコミットする必要があります。提案された値 (検証する必要がある) を渡す方法がないため、コミットされたプロパティ値のみを使用できます。

プロパティに無効な値があり、無効な値の状態がビューモデルに保持されている場合でも、ビューモデルとビューは常に同期されるため、これは良いアプローチだと思います。そのため、その情報に基づいて追加のロジックをビューモデルに含めることができます。 .

提案された値の検証をビュー モデルに反映させたい場合は、独自のカスタム インターフェイスと検証ルールを使用して実行する必要があります。

これが私がそれを達成した方法です:

IProposedValueErrorInfo.cs

using System.Globalization;

namespace WpfApplication
{
    public interface IProposedValueErrorInfo
    {
        object GetError(string propertyName, object value, CultureInfo cultureInfo);
    }
}

ProposedValueErrorValidationRule.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplication
{
    internal sealed class ProposedValueErrorValidationRule : ValidationRule
    {
        private readonly DependencyObject targetObject;
        private readonly DependencyProperty targetProperty;

        public ProposedValueErrorValidationRule(DependencyObject targetObject, DependencyProperty targetProperty)
            : base(ValidationStep.RawProposedValue, true)
        {
            if (targetObject == null)
                throw new ArgumentNullException("targetObject");
            if (targetProperty == null)
                throw new ArgumentNullException("targetProperty");

            this.targetObject = targetObject;
            this.targetProperty = targetProperty;
        }

        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            var expression = BindingOperations.GetBindingExpression(this.targetObject, this.targetProperty);
            if (expression != null)
            {
                var sourceItem = expression.DataItem as IProposedValueErrorInfo;
                if (sourceItem != null)
                {
                    var propertyName = expression.ParentBinding.Path != null ? expression.ParentBinding.Path.Path : null;
                    if (propertyName != null)
                    {
                        var error = sourceItem.GetError(propertyName, value, cultureInfo);
                        if (error != null)
                            return new ValidationResult(false, error);
                    }
                }
            }
            return ValidationResult.ValidResult;
        }
    }
}

ProposedValueValidationBindingExtension.cs

using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace WpfApplication
{
    public sealed class ProposedValueValidationBindingExtension : MarkupExtension
    {
        private readonly Binding binding;

        public ProposedValueValidationBindingExtension(Binding binding)
        {
            if (binding == null)
                throw new ArgumentNullException("binding");

            this.binding = binding;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var provideValueTarget = serviceProvider != null ? serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget : null;
            if (provideValueTarget != null)
                this.binding.ValidationRules.Add(new ProposedValueErrorValidationRule(provideValueTarget.TargetObject as DependencyObject, provideValueTarget.TargetProperty as DependencyProperty));

            return this.binding.ProvideValue(serviceProvider);
        }
    }
}

Person.cs

using System.Globalization;

namespace WpfApplication
{
    public class Person : IProposedValueErrorInfo
    {
        public int Age { get; set; }
        public string Surname { get; set; }

        #region IProposedValueErrorInfo Members

        object IProposedValueErrorInfo.GetError(string propertyName, object value, CultureInfo cultureInfo)
        {
            switch (propertyName)
            {
                case "Age":
                    int dummy;
                    return value is int || int.TryParse(value as string, NumberStyles.Integer, cultureInfo, out dummy) ? null : "Age must be a number.";
            }

            return null;
        }

        #endregion
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:Person Age="16"/>
    </Window.DataContext>
    <StackPanel>
        <TextBox Text="{local:ProposedValueValidationBinding {Binding Age}}" ToolTip="{Binding Path='(Validation.Errors)/ErrorContent', RelativeSource={RelativeSource Self}}"/>
        <TextBox Text="{local:ProposedValueValidationBinding {Binding Age}}" ToolTip="{Binding Path='(Validation.Errors)/ErrorContent', RelativeSource={RelativeSource Self}}"/>
    </StackPanel>
</Window>
于 2012-05-23T22:58:13.820 に答える