通过WPF有效性规则的状态视图模型中MVVM(Passing state of WPF Valida

2019-06-24 10:40发布

我被困在一个看似普通的要求。 我有一个WPF棱镜(用于MVVM)应用程序。 我的模型实现了IDataErrorInfo的进行验证。 该IDataErrorInfo的非数值属性的伟大工程。 然而,对于数值属性,如果用户输入无效字符(其不是数字),则数据甚至不到达模型,因为WPF不能将其转换到数字类型。

所以,我不得不使用WPF有效性规则为无效的数字输入的用户提供一些有意义的信息。 在视图中所有的按键绑定到棱镜DelegateCommand(在视图模型)和按钮的启用/禁用在视图模型本身完成。

现在,如果一个WPF有效性规则失败一些文本框,我如何将此信息传递给视图模型,以便它可以适当地在视图中禁用按钮?

Answer 1:

NIRVAN

要解决这个特定问题的最简单方法是使用数字文本框 ,它可以防止用户输入无效的值(您可以通过第三方供应商做到这一点,或者找到一个开源的解决方案,如从文本框派生的类抑制非数字输入)。

在MVVM来处理这个而不做上述的第二种方法,是定义在你的ViewModel它是一个字符串的另一个领域,该领域绑定到文本框。 然后,在你串场您可以设置整数,和值分配给您的数字字段的setter方法:

下面是一个粗略的例子:(注意我没有测试它,但它应该给你的想法)

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


Answer 2:

对于MVVM我喜欢,因为它们是可重复使用的使用附加属性这种类型的东西,它使视图模型干净。

为了绑定到Validation.HasError财产到您的视图模型,你必须创造出具有同步与您正在验证用户输入控件的属性Validation.HasError的附加属性值的CoerceValueCallback附加属性。

此文章解释了如何使用这种技术来解决,通知WPF有效性规则的错误的视图模型的问题。 该代码是在VB,所以我把它移植到C#,如果你不是一个VB的人。

附加属性

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你的文本同步。



Answer 3:

由于.NET 4.5,有效性规则具有验证方法的重载:

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


Answer 4:

您必须指定具体取决于绑定类型属性custome用户控制。 例如,如果你的财产为int类型,你必须把控制是不允许除intenger型diferent值。

逻辑你可以将在PreviewTextInput =“NumberValidationTextBox”。

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

只要插入你的逻辑或地方custome控制和你做。

Defently必须得实现MVVM验证。



Answer 5:

  1. 实现IDataErrorInfo在你的模型或视图模型依赖的绑定属性的逻辑。 您可以在这两个类实现。

  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}" /> 


Answer 6:

我和你同样的问题,但我解决以另一种方式,我用触发器禁用按钮时输入无效。 同时,结合文本应使用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>



Answer 7:

如果你提供一个自定义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 );
  }
}


Answer 8:

有人从创建VM中的依赖属性HasError这似乎是必然的Validation.HasError这里(不幸的是它在VB)解决了这个问题。 我不完全理解这些事,但它可以帮助你:

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



Answer 9:

我遇到同样的问题,一招解决它。 请参见下面的转换:

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


文章来源: Passing state of WPF ValidationRule to View Model in MVVM
标签: wpf mvvm Prism