如何让 WPF 验证冒泡到父控件?
How do you get WPF validation to bubble up to a parent control?
所以我有一个类似这个简化版本的控件:
<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));
}
}
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;
}
});
}
您的背景也会更新。
所以我有一个类似这个简化版本的控件:
<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));
}
}
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;
}
});
}
您的背景也会更新。