简体   繁体   中英

WPF - Style validation error of UserControl

I have implemented validation within my application using DataAnnotations and INotifyDataError and can successfully show when an error occurs and what the error is. I wish to change the default error template to style the textbox in which the value was entered.

This works correctly for me if the TextBox exists in the same UserControl as where the binding to the underlying data model is created.

However I have many inputs and have therefore decided to extract a UserControl to encapsulate the label and textbox. The problem is that having done this I can no longer get the textbox to indicate the error I get the default the red box around the entire control.

I have tried a couple of suggestions such as having the child control implement INotifyDataError but I have had no luck so far. This post is the same issue and I could not find a solution using it so I'm hoping with more tags this may draw more attention and a solution Show Validation Error in UserControl

Here is a small sample I have produced that shows the problem. The top entry styles the TextBox to red if an invalid age is entered but the bottom user control simply has a red box around it.

<Window 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:ValidatorTest" mc:Ignorable="d" x:Class="ValidatorTest.MainWindow"
    Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
    <ResourceDictionary>

        <CollectionViewSource x:Key="customerViewSource" d:DesignSource="{d:DesignInstance {x:Type local:Customer}, CreateList=True}"/>

        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true" >
                    <Setter Property="Foreground" Value="Red"/>
                    <Setter Property="Background" Value="MistyRose"/>
                    <Setter Property="BorderBrush" Value="Red"/>
                    <Setter Property="BorderThickness" Value="1.0"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ResourceDictionary>
</Window.Resources>
<Grid DataContext="{StaticResource customerViewSource}">
    <Grid x:Name="grid1" HorizontalAlignment="Left" Margin="19,27,0,0" VerticalAlignment="Top">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Orientation="Horizontal">
        <Label Content="Age:" HorizontalAlignment="Left" Margin="3"  VerticalAlignment="Center"/>
        <TextBox x:Name="ageTextBox" HorizontalAlignment="Left" Height="23" Margin="3"
                 Text="{Binding Age, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}" 
                 VerticalAlignment="Center" 
                 Width="120"
                 />
        </StackPanel>
        <local:UnitInput Grid.Row="1"
                         Label="Age"
                         Value="{Binding Age, Mode=TwoWay, ValidatesOnExceptions=True}"/>
    </Grid>
</Grid>

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        CollectionViewSource customerViewSource = ((CollectionViewSource)(this.FindResource("customerViewSource")));
        customerViewSource.Source = new [] { new Customer{
        Age = 26}};
    }
}



using System.ComponentModel.DataAnnotations;

public class Customer : ModelBase
{
    private double? age;

    [Range(21, 55)]
    [Required(ErrorMessage = "Age is required.")]
    public double? Age
    {
        get
        {
            return this.age;
        }
        set
        {
            this.ValidateProperty(() => this.Age, value);

            if (!double.Equals(value, this.age))
            {
                this.age = value;

                this.RaisePropertyChanged(() => this.Age);
            }
        }
    }
}

<UserControl x:Class="ValidatorTest.UnitInput"
         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" >
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <TextBlock Grid.Column="0" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Label}" VerticalAlignment="Center" Width="100"/>
    <TextBox Grid.Column="1" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="{Binding Value, Mode=TwoWay}" HorizontalContentAlignment="Right" VerticalAlignment="Top" Width="80" Margin="5"/>
</Grid>

/// <summary>
/// Interaction logic for UnitInput.xaml
/// </summary>
public partial class UnitInput : UserControl
{
    public UnitInput()
    {
        this.InitializeComponent();
    }

    public string Label
    {
        get
        {
            return (string)GetValue(LabelProperty);
        }
        set
        {
            SetValue(LabelProperty, value);
        }
    }

    // Using a DependencyProperty as the backing store for Label.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty LabelProperty =
        DependencyProperty.Register("Label", typeof(string), typeof(UnitInput), new PropertyMetadata("Label"));

    public string Value
    {
        get
        {
            return (string)GetValue(ValueProperty);
        }
        set
        {
            SetValue(ValueProperty, value);
        }
    }

    // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(string), typeof(UnitInput), new PropertyMetadata(null));
}

Thanks in advance for your suggestions.

I haven't got time to setup all of your code in a new project, but I did notice a potential problem. Try changing your Binding s in your UnitInput control:

<UserControl x:Class="ValidatorTest.UnitInput"
         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" >
    <Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UnitInput}}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Label}" VerticalAlignment="Center" Width="100"/>
        <TextBox Grid.Column="1" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="{Binding Value, Mode=TwoWay}" HorizontalContentAlignment="Right" VerticalAlignment="Top" Width="80" Margin="5"/>
    </Grid>
</UserControl>

The difference is in this line:

<Grid DataContext="{Binding RelativeSource={RelativeSource 
    AncestorType={x:Type UnitInput}}}">

The correct AncestorType value to use is the name/type of your UserControl ( UnitInput ), not the standard UserControl because that has no Label or Value properties declared in it. You would have had an error, similar to the one below, in the Output Window in Visual Studio, which should always be the first place that you look when you have data binding problems.

System.Windows.Data Error: 40 : BindingExpression path error: 'Label' property not found on 'object' ''UserControl' (HashCode=55649279)'. BindingExpression:Path=Label;...

Note that you may have further errors... this was just the first one that I saw.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM