17

私は一見一般的な要件で立ち往生しています。WPF Prism(MVVM用)アプリケーションがあります。私のモデルは、検証のためにIDataErrorInfoを実装しています。IDataErrorInfoは、数値以外のプロパティに最適です。ただし、数値プロパティの場合、ユーザーが無効な文字(数値ではない)を入力すると、wpfはデータを数値型に変換できないため、データはモデルに到達しません。

そのため、WPF ValidationRuleを使用して、無効な数値エントリに対して意味のあるメッセージをユーザーに提供する必要がありました。ビュー内のすべてのボタンは(ビューモデル内の)プリズムのDelegateCommandにバインドされており、ボタンの有効化/無効化はビューモデル自体で行われます。

ここで、一部のTextBoxでwpf ValidationRuleが失敗した場合、ビューのボタンを適切に無効にできるように、この情報をビューモデルに渡すにはどうすればよいですか?

4

9 に答える 9

11

MVVMの場合、このタイプのものには添付プロパティを使用することを好みます。これは、それらが再利用可能であり、ビューモデルをクリーンに保つためです。

Validation.HasErrorプロパティをビューモデルにバインドするには、添付プロパティの値を、ユーザー入力を検証しているコントロールのValidation.HasErrorプロパティと同期するCoerceValueCallbackを持つ添付プロパティを作成する必要があります。

この記事では、この手法を使用して、ビューモデルにWPFValidationRuleエラーを通知する問題を解決する方法について説明します。コードはVBに含まれていたので、VBを使用していない場合は、C#に移植しました。

添付プロパティ

public static class ValidationBehavior
{
    #region Attached Properties

    public static readonly DependencyProperty HasErrorProperty = DependencyProperty.RegisterAttached(
        "HasError",
        typeof(bool),
        typeof(ValidationBehavior),
        new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceHasError));

    private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached(
        "HasErrorDescriptor",
        typeof(DependencyPropertyDescriptor),
        typeof(ValidationBehavior));

    #endregion

    private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
    {
        return (DependencyPropertyDescriptor)d.GetValue(HasErrorDescriptorProperty);
    }

    private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
    {
        d.SetValue(HasErrorDescriptorProperty, value);
    }

    #region Attached Property Getters and setters

    public static bool GetHasError(DependencyObject d)
    {
        return (bool)d.GetValue(HasErrorProperty);
    }

    public static void SetHasError(DependencyObject d, bool value)
    {
        d.SetValue(HasErrorProperty, value);
    }

    #endregion

    #region CallBacks

    private static object CoerceHasError(DependencyObject d, object baseValue)
    {
        var result = (bool)baseValue;
        if (BindingOperations.IsDataBound(d, HasErrorProperty))
        {
            if (GetHasErrorDescriptor(d) == null)
            {
                var desc = DependencyPropertyDescriptor.FromProperty(System.Windows.Controls.Validation.HasErrorProperty, d.GetType());
                desc.AddValueChanged(d, OnHasErrorChanged);
                SetHasErrorDescriptor(d, desc);
                result = System.Windows.Controls.Validation.GetHasError(d);
            }
        }
        else
        {
            if (GetHasErrorDescriptor(d) != null)
            {
                var desc = GetHasErrorDescriptor(d);
                desc.RemoveValueChanged(d, OnHasErrorChanged);
                SetHasErrorDescriptor(d, null);
            }
        }
        return result;
    }
    private static void OnHasErrorChanged(object sender, EventArgs e)
    {
        var d = sender as DependencyObject;
        if (d != null)
        {
            d.SetValue(HasErrorProperty, d.GetValue(System.Windows.Controls.Validation.HasErrorProperty));
        }
    }

    #endregion
}

XAMLでの添付プロパティの使用

<Window
  x:Class="MySolution.MyProject.MainWindow"
  xmlns:v="clr-namespace:MyNamespace;assembly=MyAssembly">  
    <TextBox
      v:ValidationBehavior.HasError="{Binding MyPropertyOnMyViewModel}">
      <TextBox.Text>
        <Binding
          Path="ValidationText"
          UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <v:SomeValidationRuleInMyNamespace/>
          </Binding.ValidationRules>
        </Binding>
     </TextBox.Text>
  </TextBox>
</ Window >

これで、ビューモデルのプロパティがテキストボックスのValidation.HasErrorと同期されます。

于 2013-05-10T19:34:00.280 に答える
7

ニルヴァン

この特定の問題を解決する最も簡単な方法は、数値のテキストボックスを使用することです。これにより、ユーザーが無効な値を入力するのを防ぐことができます(これは、サードパーティベンダーを介して行うか、Textboxから派生したクラスなどのオープンソースソリューションを見つけることができます)。非数値入力を抑制します)。

上記を行わずにMVVMでこれを処理する2番目の方法は、ViewModelに文字列である別のフィールドを定義し、そのフィールドをテキストボックスにバインドすることです。次に、文字列フィールドのセッターで整数を設定し、数値フィールドに値を割り当てることができます。

大まかな例を次に示します:(注:テストしていませんが、アイデアが得られるはずです)

// original field
private int _age;
int Age 
{
   get { return _age; }
   set { 
     _age = value; 
     RaisePropertyChanged("Age");
   }
}


private string _ageStr;
string AgeStr
{
   get { return _ageStr; }
   set { 
     _ageStr = value; 
     RaisePropertyChanged("AgeStr");
     if (!String.IsNullOrEmpty(AgeStr) && IsNumeric(AgeStr) )
         Age = intVal;
    }
} 

private bool IsNumeric(string numStr)
{
   int intVal;
   return int.TryParse(AgeStr, out intVal);
}

#region IDataErrorInfo Members

    public string this[string columnName]
    {
        get
        {

            if (columnName == "AgeStr" && !IsNumeric(AgeStr)
               return "Age must be numeric";
        }
    }

    #endregion
于 2012-05-22T18:08:56.410 に答える
7

.NET 4.5以降、ValidationRuleにはValidateメソッドのオーバーロードがあります。

public ValidationResult Validate(object value, CultureInfo cultureInfo,
    BindingExpressionBase owner)

これをオーバーライドして、次の方法でビューモデルを取得できます。

public override ValidationResult Validate(object value, 
    CultureInfo cultureInfo, BindingExpressionBase owner)
{
    ValidationResult result = base.Validate(value, cultureInfo, owner);
    var vm = (YourViewModel)((BindingExpression)owner).DataItem;
    // ...
    return result;
}
于 2015-06-18T08:25:39.500 に答える
3

同じ問題がありますが、別の方法で解決します。入力が無効な場合は、トリガーを使用してボタンを無効にします。一方、テキストボックスバインディングは使用する必要がありますValidatesOnExceptions=true

<Style TargetType="{x:Type Button}">
<Style.Triggers>
    <DataTrigger Binding="{Binding ElementName=tbInput1, Path=(Validation.HasError)}" Value="True">
        <Setter Property="IsEnabled" Value="False"></Setter>
    </DataTrigger>

    <DataTrigger Binding="{Binding ElementName=tbInput2, Path=(Validation.HasError)}" Value="True">
        <Setter Property="IsEnabled" Value="False"></Setter>
    </DataTrigger>
</Style.Triggers>

于 2016-08-17T03:24:08.847 に答える
1

バインドタイプのプロパティに応じて、customeユーザーコントロールを指定する必要があります。たとえば、プロパティがintタイプの場合、intengerタイプ以外の異なる値を許可しないコントロールを配置する必要があります。

PreviewTextInput="NumberValidationTextBox"に配置できるロジック。

private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
    { 
        Regex regex = new Regex("[^0-9]+");
        e.Handled = regex.IsMatch(e.Text);
    }

ロジックを挿入するか、カスタムコントロールを配置するだけで、完了です。

防御的にmvvm検証も実装する必要があります。

于 2015-04-09T10:32:29.163 に答える
1
  1. IDataErrorInfoバインディングプロパティのロジックに応じて、モデルまたはViewmodelに実装します。両方のクラスで実装できます。

  2. これも基本検証クラスに実装します。IDataErrorInfoここでは、バインディングが機能しない場合に検証がトリガーされます。

    public virtual bool HasError
    {
        get { return _hasError; } 
        set
        {
            // if (value.Equals(_hasError)) return;
            _hasError = value;
            RaisePropertyChanged(() => HasError);
        }
    }
    
  3. 次に、グローバルクラスを追加します

    public class ProtocolSettingsLayout
    {
        public static readonly DependencyProperty MVVMHasErrorProperty = DependencyProperty.RegisterAttached("MVVMHasError", typeof(bool), typeof(ProtocolSettingsLayout), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceMVVMHasError));
    
        public static bool GetMVVMHasError(DependencyObject d)
        {
            return (bool)d.GetValue(MVVMHasErrorProperty);
        }
    
        public static void SetMVVMHasError(DependencyObject d, bool value)
        {
            d.SetValue(MVVMHasErrorProperty, value);
        }
    
        private static object CoerceMVVMHasError(DependencyObject d, Object baseValue)
        {
            bool ret = (bool)baseValue;
    
            if (BindingOperations.IsDataBound(d, MVVMHasErrorProperty))
            {
                if (GetHasErrorDescriptor(d) == null)
                {
                    DependencyPropertyDescriptor desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType());
                    desc.AddValueChanged(d, OnHasErrorChanged);
                    SetHasErrorDescriptor(d, desc);
                    ret = System.Windows.Controls.Validation.GetHasError(d);
                }
            }
            else
            {
                if (GetHasErrorDescriptor(d) != null)
                {
                    DependencyPropertyDescriptor desc = GetHasErrorDescriptor(d);
                    desc.RemoveValueChanged(d, OnHasErrorChanged);
                    SetHasErrorDescriptor(d, null);
                }
            }
            return ret;
        }
    
        private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor",
                                                                                typeof(DependencyPropertyDescriptor),
                                                                                typeof(ProtocolSettingsLayout));
    
        private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
        {
            var ret = d.GetValue(HasErrorDescriptorProperty);
            return ret as DependencyPropertyDescriptor;
        }
    
        private static void OnHasErrorChanged(object sender, EventArgs e)
        {
            DependencyObject d = sender as DependencyObject;
    
            if (d != null)
            {
                d.SetValue(MVVMHasErrorProperty, d.GetValue(Validation.HasErrorProperty));
            }
        }
    
        private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
        {
            var ret = d.GetValue(HasErrorDescriptorProperty);
            d.SetValue(HasErrorDescriptorProperty, value);
        }
    }
    
  4. xaml

    <TextBox  PreviewTextInput="NumValidationTextBox" Text="{Binding ESec, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true, NotifyOnValidationError=true, ValidatesOnExceptions=True, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, TargetNullValue='0', FallbackValue='0' }" Validation.ErrorTemplate="{StaticResource validationTemplate}" viewmodels:ProtocolSettingsLayout.MVVMHasError="{Binding Path=HasError}" />    
    
于 2015-04-09T11:31:27.593 に答える
0

カスタムValidationRule実装を提供する場合は、受け取った値を保存するだけでなく、最後の結果を保存することもできます。擬似コード:

public class IsInteger : ValidationRule
{
  private int parsedValue;

  public IsInteger() { }

  public string LastValue{ get; private set; }

  public bool LastParseSuccesfull{ get; private set; }

  public int ParsedValue{ get{ return parsedValue; } }

  public override ValidationResult Validate( object value, CultureInfo cultureInfo )
  {
    LastValue = (string) value;
    LastParseSuccesfull = Int32.TryParse( LastValue, cultureInfo, ref parsedValue );
    return new ValidationResult( LastParseSuccesfull, LastParseSuccesfull ? "not a valid number" : null );
  }
}
于 2012-05-15T08:10:23.760 に答える
0

誰かがここで(残念ながらVBで)、Validation.HasErrorにバインドされているように見えるVMに依存関係プロパティHasErrorを作成することで解決しました。私はまだそれを完全には理解していませんが、それはあなたを助けるかもしれません:

http://wpfglue.wordpress.com/2009/12/03/forwarding-the-result-of-wpf-validation-in-mvvm/

于 2012-05-18T10:45:47.757 に答える
0

私は同じ問題に遭遇し、トリックでそれを解決しました。以下のコンバーターを参照してください。

public class IntValidationConverter : IValueConverter
{
    static string[] AllValuse = new string[100000];
    static int index = 1;
    public static int StartOfErrorCodeIndex = -2000000000;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;
        if (value.ToString() == "") return null;

        int iValue = (int)(value);

        if (iValue == int.MinValue) return null;

        if (iValue >= StartOfErrorCodeIndex) return value;
        if ((iValue < IntValidationConverter.StartOfErrorCodeIndex) && (iValue > int.MinValue)) return AllValuse[StartOfErrorCodeIndex - iValue];

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return int.MinValue;
        if (value.ToString() == "") return int.MinValue;

        int result;
        bool success = int.TryParse(value.ToString(), out result);
        if (success) return result;

        index++;
        AllValuse[index] = value.ToString();
        return StartOfErrorCodeIndex - index;
    }
}
于 2016-08-15T20:27:43.903 に答える