可重用UserControl中的WPF验证错误样式?

可重用UserControl中的WPF验证错误样式?,wpf,validation,user-controls,fluentvalidation,Wpf,Validation,User Controls,Fluentvalidation,我在我的WPF应用程序中创建了一个可重用的UserControl,其中有一个文本框(在实际项目中还有其他组件)。我正在使用Fluent验证和INotifyDataErrorInfo来验证文本框中的用户输入。我的问题是,当属性绑定到UserControl的TextBox的模型出现错误时,TextBox的样式不会根据设置的样式触发。似乎UserControl文本框的my style触发器无法从模型中正确读取Validation.HasError值。那么,有没有一种方法可以获取要触发的样式并获取Use

我在我的WPF应用程序中创建了一个可重用的UserControl,其中有一个文本框(在实际项目中还有其他组件)。我正在使用Fluent验证和INotifyDataErrorInfo来验证文本框中的用户输入。我的问题是,当属性绑定到UserControl的TextBox的模型出现错误时,TextBox的样式不会根据设置的样式触发。似乎UserControl文本框的my style触发器无法从模型中正确读取Validation.HasError值。那么,有没有一种方法可以获取要触发的样式并获取UserControl文本框的错误工具提示

多年来,其他几个人也提出了这个问题,我对其中的每一个问题都进行了研究,但没有一个真正适合我。我尝试过的一件事是在UserControl.xaml中为textbox绑定提供一个通用的ValidationRule,但它不允许使用特定于模型的规则。我希望一些WPF大师最终能够接受挑战并解决这个问题!:)

如果根据我提供的代码创建一个示例项目,并将Height属性设置为小于10,则可以看到普通文本框使用工具提示获得触发的errorstyle,但UserControl的文本框获得基本的红色边框:

以下是我的简化代码: 用户控件:

  <UserControl x:Class="UserControlValidationTest.DataInputUserControl"
         x:Name="parentControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
    <Style TargetType="TextBox" x:Key="TextBoxStyle">
        <Style.Triggers>
            <Trigger Property= "Validation.HasError" Value="true">
                <Setter Property="Background" Value="Pink"/>
                <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
            </Trigger>
        </Style.Triggers>
    </Style>
</UserControl.Resources>

<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=parentControl}">
    <TextBox Name="ValueBox" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="60" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
MainWindow.xaml:

<Window x:Class="UserControlValidationTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:UserControlValidationTest"
    mc:Ignorable="d"
    Title="MainWindow" Height="100" Width="200">
<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
    <Style TargetType="TextBox">
        <Style.Triggers>
            <Trigger Property= "Validation.HasError" Value="true">
                <Setter Property="Background" Value="Pink"/>
                <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<StackPanel>
    <TextBox Text="{Binding User.Height, UpdateSourceTrigger=PropertyChanged}" Width="60" Margin="10"/>
    <local:DataInputUserControl Value="{Binding User.Height}" HorizontalAlignment="Center"/>
</StackPanel>
用户模型:

 public class UserModel : ValidatableModel
 {
    public double Height { get => GetPropertyValue<double>(); set => SetPropertyValue(value); }

    public UserModel()
    {
        ModelValidator = new UserValidator();
        Height = 180;
    }
}
公共类UserModel:ValidatableModel
{
公共双倍高度{get=>GetPropertyValue();set=>SetPropertyValue(value);}
公共用户模型()
{
ModelValidator=新的UserValidator();
高度=180;
}
}
用户验证程序:

public class UserValidator : AbstractValidator<UserModel>
{
    public UserValidator()
    {
        RuleFor(x => x.Height)
            .GreaterThan(10);
    }
}
公共类UserValidator:AbstractValidator
{
公共用户验证程序()
{
规则(x=>x.Height)
.GreaterThan(10);
}
}
可验证模型:

using FluentValidation;
using FluentValidation.Results;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public abstract class ValidatableModel : INotifyDataErrorInfo, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> propertyBackingDictionary = new Dictionary<string, object>();

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string parameter = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
    }

    protected T GetPropertyValue<T>([CallerMemberName] string propertyName = null)
    {
        if (propertyBackingDictionary.TryGetValue(propertyName, out object value))
        {
            return (T)value;
        }
        return default(T);
    }

    protected bool SetPropertyValue<T>(T newValue, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(newValue, GetPropertyValue<T>(propertyName)))
        {
            return false;
        }
        propertyBackingDictionary[propertyName] = newValue;
        OnPropertyChanged(propertyName);
        Validate();
        return true;
    }

    private ConcurrentDictionary<string, List<string>> errors = new ConcurrentDictionary<string, List<string>>();

    public IValidator ModelValidator { get; set; }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public IEnumerable GetErrors([CallerMemberName] string propertyName = null)
    {
        errors.TryGetValue(propertyName, out List<string> errorsForName);
        return errorsForName;
    }

    public bool HasErrors => errors.Count > 0;

    public void Validate()
    {
        errors.Clear();
        var validationResults = ModelValidator.Validate(this);
        foreach (var item in validationResults.Errors)
        {
            errors.TryAdd(item.PropertyName, new List<string> { item.ErrorMessage });
            OnErrorsChanged(item.PropertyName);
        }
    }
    }
}
使用FluentValidation;
使用FluentValidation。结果;
使用制度;
使用系统集合;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用系统组件模型;
使用System.Runtime.CompilerServices;
公共抽象类ValidatableModel:INotifyDataErrorInfo,INotifyPropertyChanged
{
私有只读字典属性BackingDictionary=new Dictionary();
公共事件属性更改事件处理程序属性更改;
受保护的void OnPropertyChanged([CallerMemberName]字符串参数=null)
{
PropertyChanged?.Invoke(这个,新的PropertyChangedEventArgs(参数));
}
受保护的T GetPropertyValue([CallerMemberName]字符串propertyName=null)
{
if(propertyBackingDictionary.TryGetValue(propertyName,out对象值))
{
返回(T)值;
}
返回默认值(T);
}
受保护的bool SetPropertyValue(T newValue,[CallerMemberName]字符串propertyName=null)
{
if(EqualityComparer.Default.Equals(newValue,GetPropertyValue(propertyName)))
{
返回false;
}
propertyBackingDictionary[propertyName]=newValue;
OnPropertyChanged(propertyName);
验证();
返回true;
}
私有ConcurrentDictionary错误=新建ConcurrentDictionary();
公共IValidator模型验证程序{get;set;}
公共事件事件处理程序错误更改;
public void onerschanged(字符串属性名称)
{
ErrorsChanged?.Invoke(这是新的DataErrorsChangedEventArgs(propertyName));
}
公共IEnumerable GetErrors([CallerMemberName]字符串propertyName=null)
{
errors.TryGetValue(propertyName,out-List errorsForName);
返回错误名称;
}
public bool hasrerrors=>errors.Count>0;
public void Validate()
{
错误。清除();
var validationResults=modelvalidater.Validate(这个);
foreach(validationResults.Errors中的var项)
{
errors.TryAdd(item.PropertyName,新列表{item.ErrorMessage});
OnErrorsChanged(项目.属性名称);
}
}
}
}

您看过
验证规则吗,这将使您的代码的大小减少到现在的一半,并且更具可读性。它适用于一些一般的错误验证,但据我所知,它不能用于特定于模型的验证。因此,如果我有几个模型,我就不能根据它们各自的验证器来验证它们。如果你知道怎么做,你能推荐一些代码吗?@hablahat,我也有同样的问题。您找到解决方案了吗?@JustShadow不幸的是,没有,我决定使用不同的验证方案,其中无效输入被丢弃,并显示一条消息,说明输入无效的原因。我也遇到了这个问题。真令人沮丧!Xaml样式和绑定的文档非常复杂,你在谷歌上找到的例子都有不同的库(或没有库)组合,用于验证、视图模型、神奇助手类、第三方控件等。
public class UserValidator : AbstractValidator<UserModel>
{
    public UserValidator()
    {
        RuleFor(x => x.Height)
            .GreaterThan(10);
    }
}
using FluentValidation;
using FluentValidation.Results;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public abstract class ValidatableModel : INotifyDataErrorInfo, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> propertyBackingDictionary = new Dictionary<string, object>();

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string parameter = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
    }

    protected T GetPropertyValue<T>([CallerMemberName] string propertyName = null)
    {
        if (propertyBackingDictionary.TryGetValue(propertyName, out object value))
        {
            return (T)value;
        }
        return default(T);
    }

    protected bool SetPropertyValue<T>(T newValue, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(newValue, GetPropertyValue<T>(propertyName)))
        {
            return false;
        }
        propertyBackingDictionary[propertyName] = newValue;
        OnPropertyChanged(propertyName);
        Validate();
        return true;
    }

    private ConcurrentDictionary<string, List<string>> errors = new ConcurrentDictionary<string, List<string>>();

    public IValidator ModelValidator { get; set; }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public IEnumerable GetErrors([CallerMemberName] string propertyName = null)
    {
        errors.TryGetValue(propertyName, out List<string> errorsForName);
        return errorsForName;
    }

    public bool HasErrors => errors.Count > 0;

    public void Validate()
    {
        errors.Clear();
        var validationResults = ModelValidator.Validate(this);
        foreach (var item in validationResults.Errors)
        {
            errors.TryAdd(item.PropertyName, new List<string> { item.ErrorMessage });
            OnErrorsChanged(item.PropertyName);
        }
    }
    }
}