Binding Validation.HasError property in MVVM

前端 未结 3 1001
南方客
南方客 2020-12-05 21:29

I am currently implementing a ValidationRule to check if some invalid character are in a TextBox. I am happy that setting the class I have implemented that inhe

相关标签:
3条回答
  • 2020-12-05 22:18

    all perfect work set NotifyOnValidationError="True" on binding; (or maybe with binding group also possible)

    then use

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

    sample with one textBox:

    <val:RangeRulecan be changed to ms sample agerangerule etc

    <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>
    
    0 讨论(0)
  • 2020-12-05 22:24

    In response to Anatoliy's request for an example of a non-working project:

    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>
    

    0 讨论(0)
  • 2020-12-05 22:25

    The Validation.HasError is readonly property, therefore Binding will not work with this property. This can be seen in ILSpy:

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

    As an alternative, you should see a great article which provides a solution in the form of use attached dependency properties, there you will see a detailed explanation of the example.

    Below is a full example from this article, I just translated it under C#, the original language is 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);
        }
    }
    

    As an alternative to the use of ValidationRule, in MVVM style you can try to implement IDataErrorInfo Interface. For more info see this:

    Enforcing Complex Business Data Rules with WPF

    0 讨论(0)
提交回复
热议问题