如何使WPF验证冒泡到父控件?

6

我有一个这样简化版本的控件:

<local:ImageMapField x:Class="ImageApp.WPF.Controls.ImageMapContentField"
             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" 
             xmlns:local="clr-namespace:ImageApp.WPF.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
                        x:Name="Me">

    <Grid HorizontalAlignment="Left">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" HorizontalAlignment="Stretch" Style="{DynamicResource BaseLabelStyle}">
            <TextBlock Text="{Binding Header, RelativeSource={RelativeSource AncestorType=local:ImageMapContentField, Mode=FindAncestor}}" TextWrapping="WrapWithOverflow"></TextBlock>
        </Label>
        <StackPanel Grid.Column="1">
            <Image />
            <Border Margin="20,5,5,2">
                <ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
            </Border>
        </StackPanel>
    </Grid>

</local:ImageMapField>

我正在使用它:

<controls:ImageMapContentField Header="Foo Date" 
                                FieldName="FooDate"
                                ImageSource="{Binding MyImage, Mode=TwoWay}"
                                ItemsSource="{Binding Map.Items}"
                                Zoom="{Binding MapFieldZoom}">
    <controls:ImageMapContentField.DataEntryContent>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <TextBox Grid.Column="0" Text="{Binding MyDate, StringFormat=MM/dd/yyyy, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
                <controls:WatermarkService.Watermark>
                    <TextBlock>Date</TextBlock>
                </controls:WatermarkService.Watermark>
            </TextBox>
            <TextBox Grid.Column="1" Text="{Binding MyTime, StringFormat=HH\:mm}">
                <controls:WatermarkService.Watermark>
                    <TextBlock>Time</TextBlock>
                </controls:WatermarkService.Watermark>
            </TextBox>
        </Grid>
    </controls:ImageMapContentField.DataEntryContent>
</controls:ImageMapContentField>

问题在于我没有将模型的属性绑定到ImageMapContentField上,因此ImageMapContentField上的Validation.HasError始终为false,从而无法触发。相反,我得到了默认的TextBox验证结果。我真正想要的是 ImageMapContentField 的背景变成粉色。这对我的其他控件是有效的,因为我直接绑定到了某些东西,但我无法让它在拥有ContentPresenter的控件中起作用。我希望我只是错过了一些可以使父控件捕获验证的东西。

如请求所示,以下是问题的最小示例:

MainWindow.xaml

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


    <Window.Resources>
        <Style TargetType="local:CustomTextField">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
                            <AdornedElementPlaceholder/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
                    <Setter Property="Background" Value="LightPink"/>
                </Trigger>
            </Style.Triggers>
        </Style>

        <Style TargetType="local:CustomContentControl">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
                            <AdornedElementPlaceholder/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
                    <Setter Property="Background" Value="LightPink"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Window.DataContext>
        <local:MyModel />
    </Window.DataContext>
    <StackPanel>
        <local:CustomTextField LabelText="Number 1" Value="{Binding Number1, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
        <local:CustomContentControl LabelText="Number 2">
            <local:CustomContentControl.DataEntryContent>
                <TextBox Text="{Binding Number2, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
            </local:CustomContentControl.DataEntryContent>    
        </local:CustomContentControl>
    </StackPanel>
</Window>

CustomTextField.xaml

<UserControl x:Class="WpfApp1.CustomTextField"
             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" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="Me">
        <StackPanel>
            <Label Content="{Binding ElementName=Me, Path=LabelText}" />
            <TextBox Text="{Binding ElementName=Me, Path=Value}" />
        </StackPanel>
</UserControl>

CustomTextField.cs

public partial class CustomTextField : UserControl
    {
        public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
                                                        "LabelText", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
                                                        "Value", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));

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

        public string LabelText
        {
            get { return (string) GetValue(LabelTextProperty); }
            set { SetValue(LabelTextProperty, value); }
        }

        public CustomTextField()
        {
            InitializeComponent();
        }
    }

CustomContentControl.xaml

<UserControl x:Class="WpfApp1.CustomContentControl"
             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" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="Me">
        <Grid>
            <StackPanel>
                <Label Content="{Binding ElementName=Me, Path=LabelText}" />
                <ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
            </StackPanel>
        </Grid>
</UserControl>

CustomContentControl.cs

public partial class CustomContentControl : UserControl
    {
        public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
                                                        "LabelText", typeof(string), typeof(CustomContentControl), new PropertyMetadata(default(string)));

        public static readonly DependencyProperty DataEntryContentProperty = DependencyProperty.Register(
                                                        "DataEntryContent", typeof(object), typeof(CustomContentControl), new PropertyMetadata(default(object)));

        public object DataEntryContent
        {
            get { return (object) GetValue(DataEntryContentProperty); }
            set { SetValue(DataEntryContentProperty, value); }
        }

        public string LabelText
        {
            get { return (string) GetValue(LabelTextProperty); }
            set { SetValue(LabelTextProperty, value); }
        }

        public CustomContentControl()
        {
            InitializeComponent();
        }
    }

MyModel.cs

public class MyModel : INotifyPropertyChanged
    {
        int _number1;
        int _number2;

        public int Number1
        {
            get { return _number1; }
            set
            {
                _number1 = value;
                OnPropertyChanged();
            }
        }

        public int Number2
        {
            get { return _number2; }
            set
            {
                _number2 = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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


你是否有一个完整的项目可以重现这个问题? - Simon Mourier
@SimonMourier 添加了一个示例。 - TyCobb
1个回答

6

WPF的验证已经向父控件冒泡(即使子控件位于ContentPresenter内部)- Validation.ErrorEvent

问题在于,即使事件冒泡,附加属性Validation.HasError也不会得到更新,这基本上是因为控件属性绑定中没有错误。因此,您看不到背景变化。

要纠正这个问题 - 您可以使用以下代码:

在MainWindow.xaml中更新样式

    <Style TargetType="local:CustomContentControl">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
                        <AdornedElementPlaceholder/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="HasErrors" Value="True">
                <Setter Property="Background" Value="LightPink"/>
            </Trigger>
        </Style.Triggers>
    </Style>

同时,更新CustomContentControl以添加HasErrors依赖属性和验证错误事件处理程序

    public static readonly DependencyProperty HasErrorsProperty = DependencyProperty.Register("HasErrors", typeof(bool), typeof(CustomContentControl), new PropertyMetadata(false));

    public bool HasErrors
    {
        get { return (bool)GetValue(HasErrorsProperty); }
        set { SetValue(HasErrorsProperty, value); }
    }

    public CustomContentControl()
    {
        InitializeComponent();

        Validation.AddErrorHandler(this, (s, args) => {
            if (args.Action == ValidationErrorEventAction.Added)
            {
                this.ToolTip = args.Error.ErrorContent;
                HasErrors = true;
            }
            else
            {
                this.ToolTip = null;
                HasErrors = false;
            }
        });  
    }

并且您的背景将被更新。 在此输入图像描述

谢谢!我就知道我漏掉了什么东西。希望以某种方式触发标准的 Validation.HasError,这样就像其他所有东西一样,但这个也可以工作,并且相当简单。 - TyCobb
是的 - 我的第一反应也是手动向Validation.Errors集合添加一个错误,以触发Validation.HasError中的更新。但引入自定义依赖属性似乎更简单; 特别是如果您只有一个要跟踪的绑定验证。如果您有多个绑定要跟踪(多个子控件)-则建议使用Validation.Errors来跟踪错误。 https://dev59.com/P3I95IYBdhLWcg3wvwoQ - Sharada Gururaj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接