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 中。 BorderValidationErrorElement 是分开的元素,以便为鼠标悬停、聚焦...应用不同的视觉状态...

<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>