8912


WPFアプリケーションをMVVMで開発しています。  
VS2013/.NET4.5/C#です。

`TextBox`などに適用した`IValueConverter`の`ConvertBack`で型の変換に失敗したとき、それをViewで表示するとともに、ViewModelからも任意のタイミングでその情報を得られるようにしたいです。  
例えば、エラーが解消されないうちは、登録処理を実行できないようにする、といった制御を考えています。

`ConvertBack`で発生したエラーをViewに通知する方法は、[英語版StackOverflowの質問](https://stackoverflow.com/questions/6439527/ivalueconverter-
and-validation-on-
exception)を見て、`ValidationResult`を返すという方法で実現することができましたが、ViewModelからそれを知る方法が分かりません。  
[こちらのページ](http://okazuki.hatenablog.com/entry/20110118/1295338167)で検証エラーをViewModelから知る方法が掲載されていましたが、上記の英語版StackOverflowのやり方との組み合わせでは、検証エラーのイベントが発生せず（`private
void ErrorHandler`が呼ばれない）、実現できませんでした。  
もしかすると、使い方が間違っているだけで、本当は`IValueConverter.ConvertBack`で`ValidationResult`を返すと、検証エラーのイベントが発生するはずなのでしょうか？  
あるいは他に、正しいやり方があるのでしょうか？


CC BY-SA 3.0
2015-04-08T01:31:20.963
8887
2015-04-09T11:36:23.787
2017-05-23T12:38:56.467
-1
8078
question
c# wpf
IValueConverterでの変換エラーをViewModelから検知したい


`ValidationResult`は`ValidationRule.Validate`が返すべき戻り値です。`IValueConverter`ではなく`ValidationRule`派生クラスを実装し、`Binding.ValidationRules`に追加してやれば`Validation`クラスの添付プロパティに反映されると思います。

## 追記：Validation.ErrorsからVMへの通知方法

`Validation.HasError`のみを扱うのであれば[本文のリンク先](http://okazuki.hatenablog.com/entry/20110118/1295338167)にある`ValidationErrorBehavior`で良いと思います。

`Validation.Errors`の場合はコレクションであるため多少複雑になります。  
`Blend SDK`を使わない形で書くと以下のような感じです。

### プロパティ

[code]

    // 適当なクラスを定義する
    public static class Behaviors
    {
        // エラー通知有効化用の添付プロパティ
        public static bool GetIsEnabled(FrameworkElement obj)
        {
            return (bool)obj.GetValue(IsEnabledProperty);
        }
    
        public static void SetIsEnabled(FrameworkElement obj, bool value)
        {
            obj.SetValue(IsEnabledProperty, value);
        }
    
        public static readonly DependencyProperty IsEnabledProperty
            = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(Behaviors), new FrameworkPropertyMetadata(false, IsEnabledChanged) { BindsTwoWayByDefault = true });
    
        // IsEnabledPropertyの変更通知コールバック
        private static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var fe = (FrameworkElement)d;
    
            // Behaviors.IsEnabledとIsLoadedの値で4分岐する
    
            if (true.Equals(e.NewValue))
            {
                if (fe.IsLoaded)
                {
                    // 有効・初期化済み
    
                    // エラー通知の初期設定
                    fe.SetValue(ErrorsProperty, Validation.GetErrors(fe));
                    Validation.AddErrorHandler(fe, fe_Error);
                }
                else
                {
                    // 有効・初期化途中
                    fe.Loaded += fe_Loaded;
                }
            }
            else
            {
                if (fe.IsLoaded)
                {
                    // 無効・初期化済み
    
                    // エラー通知の解除処理
                    fe.SetValue(ErrorsProperty, null);
                    Validation.RemoveErrorHandler(fe, fe_Error);
                }
                else
                {
                    // 無効・初期化途中
                    fe.Loaded -= fe_Loaded;
                }
            }
        }
    
        // 初期化途中にエラー通知が有効化された場合の初期化完了時の処理
        private static void fe_Loaded(object sender, RoutedEventArgs e)
        {
            var fe = (FrameworkElement)sender;
            fe.Loaded -= fe_Loaded;
    
            // エラー通知の初期設定
            fe.SetValue(ErrorsProperty, Validation.GetErrors(fe));
            Validation.AddErrorHandler(fe, fe_Error);
        }
    
        // FrameworkElementからエラー通知を受け取った場合の処理
        private static void fe_Error(object sender, ValidationErrorEventArgs e)
        {
            // Validation.ErrorsをBehaviors.Errorに設定
            var fe = (FrameworkElement)sender;
            fe.SetValue(ErrorsProperty, Validation.GetErrors(fe));
        }
    
        // 以下Errorsの設定用添付プロパティ
    
        public static ReadOnlyObservableCollection<ValidationError> GetErrors(FrameworkElement obj)
        {
            return (ReadOnlyObservableCollection<ValidationError>)obj.GetValue(ErrorsProperty);
        }
    
        public static void SetErrors(FrameworkElement obj, ReadOnlyObservableCollection<ValidationError> value)
        {
            obj.SetValue(ErrorsProperty, value);
        }
    
        public static readonly DependencyProperty ErrorsProperty
            = DependencyProperty.RegisterAttached("Errors", typeof(ReadOnlyObservableCollection<ValidationError>), typeof(Behaviors), new FrameworkPropertyMetadata(null));
    }
    
[/code]

### VM

[code]

    // エラーを受け取るプロパティ
    private ReadOnlyObservableCollection<ValidationError> _Errors;
    
    public ReadOnlyObservableCollection<ValidationError> Errors
    {
        get
        {
            return _Errors;
        }
        set
        {
            if (value != _Errors)
            {
                if (_Errors != null)
                {
                    ((INotifyCollectionChanged)_Errors).CollectionChanged -= Errors_CollectionChanged;
                }
                _Errors = value;
                if (_Errors != null)
                {
                    ((INotifyCollectionChanged)_Errors).CollectionChanged += Errors_CollectionChanged;
                }
    
                OnErrorsChanged();
    
                // TODO:INotifyPropertyChangedであればPropertyChangedを発生させる
            }
        }
    }
    
    // コレクションの中身が変わった場合の処理
    private void Errors_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        OnErrorsChanged();
    }
    
    private void OnErrorsChanged()
    {
        // TODO:Validation.Errors変更時の処理
    }
    
[/code]

### XAML

[code]

    <TextBox
        my:Behaviors.IsEnabled="True"
        my:Behaviors.Errors="{Binding Errors, Mode=OneWayToSource}"
        Text="{Binding NumberProperty, NotifyOnValidationError=True}" />
    
[/code]

原理的には上の3つで良いはずですが、VMの実装をプロパティ毎に作っていたら恐ろしいことになるので値とエラーを共通クラス化するとか、`MarkupExtension`を使ってXAMLだけでスッキリ書けるようにするなどの工夫は必要だと思います。


CC BY-SA 3.0
2015-04-08T11:00:10.187
8912
2015-04-09T11:36:23.787
2015-04-09T11:36:23.787
5750
5750
8887
answer