WPF - 文本框验证错误显示两次但未删除
WPF - TextBox validation error displayed twice and not removed
问题描述
I'm developing an application in which I have a ListBox
where, when an element is selected, it's details are shown in an editing control.
我将 SelectedItem
绑定到控件,并且因为我想应用不同的 DataTemplates
,我正在尝试使用 VM 优先方法并直接绑定到控件内容。
我的自定义 TextBox
样式显示带有蓝色边框的验证错误(为了举例)。但是,使用这种方法时,还会显示红色验证边框,并且一旦数据正确,它就不会被删除。 这不是预期的行为,红色边框根本不应该显示。
不知道错误是在style还是在binding.
示例和测试
我尝试了不同的方法来尝试调试错误。这不会发生在标准样式和 DataContext
方法中。但是,我不能使用 DataContext
方法,因为我需要将不同的模板应用于列表中不同类型的元素。
见下图。
当数据无效(空)时,“VM First + Custom style” 选项同时显示蓝色和红色边框:
当我写一些文字时,红色边框没有被删除:
视图模型
有两个 ViewModel,一个用于主要 window,另一个用于列表中的每个元素:
public class ChildViewModel : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public override string this[string propertyName]
{
get
{
if (propertyName == nameof(Name))
{
if (string.IsNullOrEmpty(Name))
{
return "The name is mandatory.";
}
}
return base[propertyName];
}
}
}
public class ParentViewModel : ViewModelBase
{
private ChildViewModel _selectedItem;
public ObservableCollection<ChildViewModel> Collection { get; private set; }
public ChildViewModel SelectedItem
{
get => _selectedItem;
set => SetProperty(ref _selectedItem, value);
}
public ParentViewModel()
{
Collection = new ObservableCollection<ChildViewModel>();
Collection.Add(new ChildViewModel());
Collection.Add(new ChildViewModel());
}
}
public abstract class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string Error => this[null];
public virtual string this[string propertyName] => string.Empty;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
NotifyPropertyChanged(propertyName);
}
}
}
观看次数
MainWindow
如下,除标准外无代码隐藏
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApp"
Title="MainWindow" Height="300" Width="400">
<Window.DataContext>
<local:ParentViewModel />
</Window.DataContext>
<Window.Resources>
<ResourceDictionary Source="Style.xaml" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox Grid.RowSpan="2" ItemsSource="{Binding Collection, Mode=OneWay}" SelectedItem="{Binding SelectedItem}"/>
<UserControl Grid.Column="1" Grid.Row="0" DataContext="{Binding SelectedItem}">
<StackPanel Orientation="Vertical">
<Label Content="DataContext + No style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="DataContext + Custom style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TextBoxStyle}" />
</StackPanel>
</UserControl>
<ContentControl Grid.Column="1" Grid.Row="1" Content="{Binding SelectedItem}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ChildViewModel}">
<StackPanel Orientation="Vertical">
<Label Content="VM First + No style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="VM First + Custom style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TextBoxStyle}" />
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
风格
它位于名为“Style.xaml”的 ResourceDictionary
中。 Border
和 ValidationErrorElement
是分开的元素,以便为鼠标悬停、聚焦...应用不同的视觉状态...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid x:Name="RootElement">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border" BorderThickness="1" BorderBrush="Black" Opacity="1">
<Grid>
<ScrollViewer x:Name="PART_ContentHost" BorderThickness="0" IsTabStop="False" Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
<Border x:Name="ValidationErrorElement" BorderBrush="Blue" BorderThickness="1" Visibility="Collapsed">
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
您没有正确显示错误情况。
WPF 为此使用带有 AdornedElementPlaceholder 的 ControlTemplate,它在附件 Validation.ErrorTemplate 属性.
中设置
可以在此处找到一个重要的实施示例:
这是来自另一个小例子的 XAML 的一小段:
<Window.Resources>
<ControlTemplate x:Key="validationFailed">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Violet" BorderThickness="2">
<AdornedElementPlaceholder />
</Border>
<TextBlock Foreground="Red" FontSize="26" FontWeight="Bold">!</TextBlock>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox Margin="10"
Validation.ErrorTemplate="{StaticResource validationFailed}" >
<TextBox.Text>
<Binding Path="Age">
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
问题描述
I'm developing an application in which I have a ListBox
where, when an element is selected, it's details are shown in an editing control.
我将 SelectedItem
绑定到控件,并且因为我想应用不同的 DataTemplates
,我正在尝试使用 VM 优先方法并直接绑定到控件内容。
我的自定义 TextBox
样式显示带有蓝色边框的验证错误(为了举例)。但是,使用这种方法时,还会显示红色验证边框,并且一旦数据正确,它就不会被删除。 这不是预期的行为,红色边框根本不应该显示。
不知道错误是在style还是在binding.
示例和测试
我尝试了不同的方法来尝试调试错误。这不会发生在标准样式和 DataContext
方法中。但是,我不能使用 DataContext
方法,因为我需要将不同的模板应用于列表中不同类型的元素。
见下图。
当数据无效(空)时,“VM First + Custom style” 选项同时显示蓝色和红色边框:
当我写一些文字时,红色边框没有被删除:
视图模型
有两个 ViewModel,一个用于主要 window,另一个用于列表中的每个元素:
public class ChildViewModel : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public override string this[string propertyName]
{
get
{
if (propertyName == nameof(Name))
{
if (string.IsNullOrEmpty(Name))
{
return "The name is mandatory.";
}
}
return base[propertyName];
}
}
}
public class ParentViewModel : ViewModelBase
{
private ChildViewModel _selectedItem;
public ObservableCollection<ChildViewModel> Collection { get; private set; }
public ChildViewModel SelectedItem
{
get => _selectedItem;
set => SetProperty(ref _selectedItem, value);
}
public ParentViewModel()
{
Collection = new ObservableCollection<ChildViewModel>();
Collection.Add(new ChildViewModel());
Collection.Add(new ChildViewModel());
}
}
public abstract class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string Error => this[null];
public virtual string this[string propertyName] => string.Empty;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
NotifyPropertyChanged(propertyName);
}
}
}
观看次数
MainWindow
如下,除标准外无代码隐藏
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApp"
Title="MainWindow" Height="300" Width="400">
<Window.DataContext>
<local:ParentViewModel />
</Window.DataContext>
<Window.Resources>
<ResourceDictionary Source="Style.xaml" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox Grid.RowSpan="2" ItemsSource="{Binding Collection, Mode=OneWay}" SelectedItem="{Binding SelectedItem}"/>
<UserControl Grid.Column="1" Grid.Row="0" DataContext="{Binding SelectedItem}">
<StackPanel Orientation="Vertical">
<Label Content="DataContext + No style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="DataContext + Custom style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TextBoxStyle}" />
</StackPanel>
</UserControl>
<ContentControl Grid.Column="1" Grid.Row="1" Content="{Binding SelectedItem}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ChildViewModel}">
<StackPanel Orientation="Vertical">
<Label Content="VM First + No style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="VM First + Custom style" />
<TextBox Margin="6" Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TextBoxStyle}" />
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
风格
它位于名为“Style.xaml”的 ResourceDictionary
中。 Border
和 ValidationErrorElement
是分开的元素,以便为鼠标悬停、聚焦...应用不同的视觉状态...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid x:Name="RootElement">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border" BorderThickness="1" BorderBrush="Black" Opacity="1">
<Grid>
<ScrollViewer x:Name="PART_ContentHost" BorderThickness="0" IsTabStop="False" Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
<Border x:Name="ValidationErrorElement" BorderBrush="Blue" BorderThickness="1" Visibility="Collapsed">
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
您没有正确显示错误情况。
WPF 为此使用带有 AdornedElementPlaceholder 的 ControlTemplate,它在附件 Validation.ErrorTemplate 属性.
可以在此处找到一个重要的实施示例:
这是来自另一个小例子的 XAML 的一小段:
<Window.Resources>
<ControlTemplate x:Key="validationFailed">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Violet" BorderThickness="2">
<AdornedElementPlaceholder />
</Border>
<TextBlock Foreground="Red" FontSize="26" FontWeight="Bold">!</TextBlock>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox Margin="10"
Validation.ErrorTemplate="{StaticResource validationFailed}" >
<TextBox.Text>
<Binding Path="Age">
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>