可重用的UserControl中是否存在WPF验证错误样式?

时间:2019-04-05 12:05:43

标签: wpf validation user-controls fluentvalidation

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

多年来,其他几个人都曾问过这个问题,我查看了其中的每个人,但没有一个人真正为我工作。我试图起作用的一件事是UserControl.xaml中用于文本框绑定的常规ValidationRule,但这不允许模型特定的规则。我希望一些WPF专家最终能够挑战并解决这个问题! :)

如果从我提供的代码中创建一个示例项目,并将Height属性设置为小于10,您会看到普通的TextBox带有工具提示会触发错误样式,而UserControl的TextBox则会显示基本的红色边框: Sample app with the cursor over the first textbox.

这是我的简化代码: 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>

public partial class DataInputUserControl : UserControl
{
    public DataInputUserControl()
    {
        InitializeComponent();
        Validation.SetValidationAdornerSite(this, ValueBox);
    }

    public double Value
    {
        get => (double)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(double), typeof(DataInputUserControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}

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 MainWindowViewModel
{
    public UserModel User { get; set; }
    public MainWindowViewModel()
    {
        User = new UserModel();
    }
}

用户模型:

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

    public UserModel()
    {
        ModelValidator = new UserValidator();
        Height = 180;
    }
}

用户验证器:

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

0 个答案:

没有答案