在MVVM中绑定Validation.HasError属性

时间:2014-04-03 03:43:31

标签: c# wpf validation mvvm binding

我目前正在实施ValidationRule来检查TextBox中是否存在某些无效字符。我很高兴设置我已实现的类在我的TextBox上继承ValidationRule,在找到这些字符时将其设置为红色,但我还想使用Validation.HasError属性或Validation.Errors属性弹出一个消息框,告诉用户页面中各种文本框中存在错误。

有没有办法将我的ViewModel中的属性绑定到 Validation.HasError 和/或Validation.Errors属性,以便我可以访问它们在我的ViewModel中?

这是TextBox的错误样式:

<Style x:Key="ErrorValidationTextBox" TargetType="{x:Type pres:OneTextBox}">
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel LastChildFill="True">
                    <TextBlock DockPanel.Dock="Right"
                    Foreground="Red"
                    FontSize="12pt"
                    Text="{Binding ElementName=MyAdorner, 
                           Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                    </TextBlock>
                    <AdornedElementPlaceholder x:Name="MyAdorner"/>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

以下是我在我的XAML中声明我的TextBox(OneTextBox封装常规WPF TextBox)的方法:

<pres:OneTextBox Watermark="Name..." Margin="85,12,0,0" Style="{StaticResource ErrorValidationTextBox}"
                 AcceptsReturn="False" MaxLines="1" Height="22" VerticalAlignment="Top"
                 HorizontalAlignment="Left" Width="300" >
    <pres:OneTextBox.Text>
        <Binding Path="InterfaceSpecification.Name" UpdateSourceTrigger="PropertyChanged">                    
            <Binding.ValidationRules>                       
                <interfaceSpecsModule:NoInvalidCharsRule/>                        
            </Binding.ValidationRules>                    
        </Binding>               
    </pres:OneTextBox.Text>        
</pres:OneTextBox>

3 个答案:

答案 0 :(得分:7)

Validation.HasError readonly 属性,因此Binding不适用于此属性。这可以在ILSpy

中看到
public virtual bool HasError
{
    get
    {
        return this._validationError != null;
    }
}

作为替代方案,您应该会看到一个很棒的article,它以使用附加依赖项属性的形式提供解决方案,在那里您将看到该示例的详细说明。

以下是本文的完整示例,我刚刚将其翻译为C#,原始语言为VB.NET

<强> XAML

<Window x:Class="HasErrorTestValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:HasErrorTestValidation"
        WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:TestData />
    </Window.DataContext>

    <StackPanel>
        <TextBox x:Name="TestTextBox" 
                 local:ProtocolSettingsLayout.MVVMHasError="{Binding Path=HasError}">
            <TextBox.Text>
                <Binding Path="TestText" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <local:OnlyNumbersValidationRule ValidatesOnTargetUpdated="True"/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>

        <TextBlock>
            <TextBlock.Text>
                <Binding Path="HasError" StringFormat="HasError is {0}"/>
            </TextBlock.Text>
        </TextBlock>

        <TextBlock>
            <TextBlock.Text>
                <Binding Path="(Validation.HasError)" ElementName="TestTextBox" StringFormat="Validation.HasError is {0}"/>
            </TextBlock.Text>
        </TextBlock>        
    </StackPanel>
</Window>

<强> Code-behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

#region Model

public class TestData : INotifyPropertyChanged
{
    private bool _hasError = false;

    public bool HasError
    {
        get
        {
            return _hasError;
        }

        set
        {
            _hasError = value;
            NotifyPropertyChanged("HasError");
        }
    }

    private string _testText = "0";

    public string TestText
    {
        get
        {
            return _testText;
        }

        set
        {
            _testText = value;
            NotifyPropertyChanged("TestText");
        }
    }

    #region PropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string sProp)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(sProp));
        }
    }

    #endregion
}

#endregion

#region ValidationRule

public class OnlyNumbersValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var result = new ValidationResult(true, null);

        string NumberPattern = @"^[0-9-]+$";
        Regex rgx = new Regex(NumberPattern);

        if (rgx.IsMatch(value.ToString()) == false)
        {
            result = new ValidationResult(false, "Must be only numbers");
        }

        return result;
    }
}

#endregion

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

作为使用ValidationRule的替代方法,在MVVM样式中,您可以尝试实现IDataErrorInfo接口。有关详细信息,请参阅:

Enforcing Complex Business Data Rules with WPF

答案 1 :(得分:0)

回应Anatoliy要求提供非工作项目的例子:

Generic.xaml

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestAttachedPropertyValidationError">


<Style TargetType="{x:Type local:TextBoxCustomControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:TextBoxCustomControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="10"/>
                            <ColumnDefinition Width="50"/>
                        </Grid.ColumnDefinitions>
                        <Grid.Resources>
                            <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
                        </Grid.Resources>
                        <Label 
                            Grid.Row ="0" 
                            Grid.Column="0" 
                            Content="Enter a numeric value:" />
                        <TextBox 
                            Grid.Row ="0" 
                            Grid.Column="2" 
                            local:HasErrorUtility.HasError="{Binding NumericPropHasError, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                            Text="{Binding NumericProp, Mode=TwoWay, UpdateSourceTrigger=LostFocus, RelativeSource={RelativeSource TemplatedParent}}" />
                        <Label 
                            Grid.Row ="1" 
                            Grid.Column="0" 
                            Content="Value entered:" />
                        <Label 
                            Grid.Row ="1" 
                            Grid.Column="2" 
                            Content="{TemplateBinding NumericProp}" />
                        <Label 
                            Grid.Row ="2" 
                            Grid.Column="0" 
                            Grid.ColumnSpan="3" 
                            Visibility="{TemplateBinding NumericPropHasError, Converter={StaticResource BooleanToVisibilityConverter}}"
                            Foreground="Red" 
                            Content="Not a numeric value" />
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

TextBoxCustomControl.cs

using System.Windows;
using System.Windows.Controls;

namespace TestAttachedPropertyValidationError
{
    public class TextBoxCustomControl : Control
    {
        static TextBoxCustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxCustomControl), new FrameworkPropertyMetadata(typeof(TextBoxCustomControl)));
        }

        public static readonly DependencyProperty NumericPropProperty =
            DependencyProperty.Register("NumericProp", typeof (int), typeof (TextBoxCustomControl), new PropertyMetadata(default(int)));

        public int NumericProp
        {
            get { return (int) GetValue(NumericPropProperty); }
            set { SetValue(NumericPropProperty, value); }
        }

        public static readonly DependencyProperty NumericPropHasErrorProperty =
            DependencyProperty.Register("NumericPropHasError", typeof (bool), typeof (TextBoxCustomControl), new PropertyMetadata(default(bool)));

        public bool NumericPropHasError
        {
            get { return (bool) GetValue(NumericPropHasErrorProperty); }
            set { SetValue(NumericPropHasErrorProperty, value); }
        }
    }
}

HasErrorUtility.cs

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace TestAttachedPropertyValidationError
{
    class HasErrorUtility
    {
        public static readonly DependencyProperty HasErrorProperty = DependencyProperty.RegisterAttached("HasError",
                                                                        typeof(bool),
                                                                        typeof(HasErrorUtility),
                                                                        new FrameworkPropertyMetadata(false,
                                                                                                      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                                                                      null,
                                                                                                      CoerceHasError));

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

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

        private static object CoerceHasError(DependencyObject d, Object baseValue)
        {
            var ret = (bool)baseValue;
            if (BindingOperations.IsDataBound(d, HasErrorProperty))
            {
                if (GetHasErrorDescriptor(d) == null)
                {
                    var desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType());
                    desc.AddValueChanged(d, OnHasErrorChanged);
                    SetHasErrorDescriptor(d, desc);
                    ret = Validation.GetHasError(d);
                }
            }
            else
            {
                if (GetHasErrorDescriptor(d) != null)
                {
                    var desc = GetHasErrorDescriptor(d);
                    desc.RemoveValueChanged(d, OnHasErrorChanged);
                    SetHasErrorDescriptor(d, null);
                }
            }

            return ret;
        }

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

        private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
        {
            var ret = d.GetValue(HasErrorDescriptorProperty);
            return ret as DependencyPropertyDescriptor;
        }

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

        private static void OnHasErrorChanged(object sender, EventArgs e)
        {
            var d = sender as DependencyObject;

            if (d != null)
            {
                d.SetValue(HasErrorProperty, d.GetValue(Validation.HasErrorProperty));
            }
        }

    }
}

ViewModel.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace TestAttachedPropertyValidationError
{
    public class ViewModel :INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private int _vmNumericProp;
        private bool _vmNumericPropHasError;

        public int VmNumericProp
        {
            get { return _vmNumericProp; }
            set
            {
                _vmNumericProp = value;
                OnPropertyChanged();
            }
        }

        public bool VmNumericPropHasError
        {
            get { return _vmNumericPropHasError; }
            set
            {
                _vmNumericPropHasError = value;
                OnPropertyChanged();
            }
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MainWindow.xaml

<Window x:Class="TestAttachedPropertyValidationError.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestAttachedPropertyValidationError"
    Title="MainWindow" Height="350" Width="525">
<StackPanel Margin="10">
    <StackPanel.Resources>
        <local:ViewModel x:Key="VM1"/>
        <local:ViewModel x:Key="VM2"/>
    </StackPanel.Resources>
    <Label Content="Custom Control...}"></Label>
    <local:TextBoxCustomControl 
        Margin="10" 
        DataContext="{StaticResource VM1}"
        NumericProp="{Binding VmNumericProp}"
        NumericPropHasError="{Binding VmNumericPropHasError}"/>
    <Label Content="Regular XAML...}" Margin="0,20,0,0"/>
    <Grid 
        Margin="10"
        DataContext="{StaticResource VM2}"
        >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="50"/>
        </Grid.ColumnDefinitions>
        <Grid.Resources>
            <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
        </Grid.Resources>
        <Label 
                            Grid.Row ="0" 
                            Grid.Column="0" 
                            Content="Enter a numeric value:" />
        <TextBox 
                            Grid.Row ="0" 
                            Grid.Column="2" 
                            local:HasErrorUtility.HasError="{Binding VmNumericPropHasError, Mode=TwoWay}"
                            Text="{Binding VmNumericProp, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
        <Label 
                            Grid.Row ="1" 
                            Grid.Column="0" 
                            Content="Value entered:" />
        <Label 
                            Grid.Row ="1" 
                            Grid.Column="2" 
                            Content="{Binding VmNumericProp}" />
        <Label 
                            Grid.Row ="2" 
                            Grid.Column="0" 
                            Grid.ColumnSpan="3" 
                            Visibility="{Binding VmNumericPropHasError, Converter={StaticResource BooleanToVisibilityConverter}}"
                            Foreground="Red" 
                            Content="Not a numeric value" />
    </Grid>

</StackPanel>

答案 2 :(得分:0)

所有具有约束力的完美作品集NotifyOnValidationError="True"; (或者也可以带有结合基团)

然后使用

<Button IsEnabled="{Binding ElementName=tbPeriod, Path=(Validation.HasError)}"

带有一个文本框的示例:

<val:RangeRule可以更改为ms样品agerangerule等

<TextBox MaxLength="5" x:Name="tbPeriod" HorizontalAlignment="Left" VerticalAlignment="Top" Width="162" Margin="10,10,0,0" Style="{StaticResource TextBoxInError}">
            <TextBox.Text>
                <Binding Path="ReportPeriod" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
                    <Binding.ValidationRules>
                        <val:RangeRule Min="70" Max="5000" />
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>