将WPF ValidationRule的状态传递到MVVM中的视图模型

brc7rcf0  于 2023-03-19  发布在  其他
关注(0)|答案(9)|浏览(261)

我陷入了一个看似常见的需求中。我有一个WPF Prism(用于MVVM)应用程序。我的模型实现了IDataErrorInfo用于验证。IDataErrorInfo对于非数值属性非常有效。然而,对于数值属性,如果用户输入了无效字符(非数值),那么数据甚至不会到达模型,因为wpf无法将其转换为数值类型。
因此,我不得不使用WPF ValidationRule来为用户提供一些有意义的消息,用于无效的数字条目。视图中的所有按钮都绑定到prism的DelegateCommand(在视图模型中),按钮的启用/禁用在视图模型本身中完成。
现在,如果某个文本框的wpf ValidationRule失败,我如何将此信息传递给视图模型,以便它可以适当地禁用视图中的按钮?

bqjvbblv

bqjvbblv1#

对于MVVM,我更喜欢使用Attached Properties来处理这类事情,因为它们是可重用的,并且可以保持视图模型的整洁。
为了将Validation.HasError属性绑定到视图模型,必须创建一个附加属性,该属性具有一个CoergeValueCallback,用于将附加属性的值与验证用户输入的控件上的Validation.HasError属性同步。
This文章解释了如何使用此技术来解决通知视图模型WPF ValidationRule错误的问题。代码是在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同步。

1bqhqjot

1bqhqjot2#

尼尔万
解决这个问题最简单的方法是使用一个numeric textbox,它可以防止用户输入无效值(您可以通过第三方供应商来实现这一点,或者找到一个开源解决方案,比如从Textbox派生的类,它可以禁止非数字输入)。
在MVVM中处理这个问题的第二种方法是在ViewModel中定义另一个字符串字段,并将该字段绑定到文本框,然后在字符串字段的setter中设置Integer,并为数值字段赋值:
下面是一个粗略的例子:(注意,我没有测试它,但它应该给予你的想法)

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

s6fujrry3#

从.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;
}
sqserrrh

sqserrrh4#

我和你有同样的问题,但是我用另一种方法解决,我用触发器在输入无效时禁用按钮。同时,文本框绑定应该使用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>
b1zrtrql

b1zrtrql5#

您必须根据绑定类型属性指定自定义用户控件。例如,如果您的属性是int类型,则必须放置不允许除integer类型之外的其他值的控件。
您可以放入PreviewTextInput=“数字验证文本框”中的逻辑。

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

只要插入您逻辑或放置custome控件,您就完成了。
默认情况下也必须实现mvvm验证。

ztmd8pv5

ztmd8pv56#

1.根据绑定属性的逻辑在模型或视图模型中实现IDataErrorInfo。您可以在这两个类中实现。
1.在你的基本验证类中也实现这个。当绑定IDataErrorInfo不起作用时,验证将在这里触发。

public virtual bool HasError
{
    get { return _hasError; } 
    set
    {
        // if (value.Equals(_hasError)) return;
        _hasError = value;
        RaisePropertyChanged(() => HasError);
    }
}

1.接下来,添加全局类

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

1.克萨姆勒

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

l3zydbqr7#

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

wbgh16ku8#

有人在这里解决了这个问题(不幸的是它是在VB中),在VM中创建了一个依赖属性HasError,它似乎绑定到Validation.HasError。我还没有完全理解它,但它可能会帮助你:
http://wpfglue.wordpress.com/2009/12/03/forwarding-the-result-of-wpf-validation-in-mvvm/

hyrbngr7

hyrbngr79#

我遇到了同样的问题,并解决了它与一个技巧。见下面的转换器:

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

相关问题