简体   繁体   English

WPF - 文本框验证错误显示两次但未删除

[英]WPF - TextBox validation error displayed twice and not removed

Issue description问题描述

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

I'm binding the SelectedItem to the control and, as I want to apply different DataTemplates , I'm trying to use VM first approach and bind directly to the control contents.我将SelectedItem绑定到控件,因为我想应用不同的DataTemplates ,所以我尝试使用 VM 优先方法并直接绑定到控件内容。

My custom TextBox style displays validation errors with a blue border (for the sake of the example).我的自定义TextBox样式显示带有蓝色边框的验证错误(为了示例)。 However, when using this approach, a red validation border is also shown and it's not being removed once the data is correct.但是,使用这种方法时,还会显示红色验证边框,并且一旦数据正确,它就不会被删除。 This is not the expected behavior , the red border should not show at all.这不是预期的行为,红色边框根本不应该显示。

I don't know if the error is in the style or in the binding .不知道是样式错误还是绑定错误。

Example and testing示例和测试

I've tried different things to try to debug the error.我尝试了不同的方法来尝试调试错误。 This is not happening with the standard style nor with a DataContext approach.这不会发生在标准样式或DataContext方法中。 However, I cannot use the DataContext approach as I will need to apply different templates to different types of elements in the list.但是,我不能使用DataContext方法,因为我需要将不同的模板应用于列表中不同类型的元素。

See the pictures below.请参阅下面的图片。

When the data is invalid (empty) the "VM First + Custom style" option shows both the blue and the red borders:当数据无效(空)时, “VM First + Custom style”选项同时显示蓝色和红色边框:

有错误边界的数据

When I write some text, the red border is not removed:当我写一些文字时,红色边框没有被移除:

错误边界应该消失的有效数据

ViewModels视图模型

There are two ViewModels, one for the main window and another for each element in the list:有两个 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);
        }
    }
}

Views观点

The MainWindow is just as follows, with no code behind apart from the standard. 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风格

This is located in a ResourceDictionary named "Style.xaml".它位于名为“Style.xaml”的ResourceDictionary中。 The Border and the ValidationErrorElement are separated elements in order to apply different visual states for mouse over, focused... 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>

You are not displaying the error condition correctly.您没有正确显示错误情况。
WPF uses a ControlTemplate with an AdornedElementPlaceholder for this, which is set in the attached Validation.ErrorTemplate property. WPF 为此使用了带有 AdornedElementPlaceholder 的 ControlTemplate,这是在附加的 Validation.ErrorTemplate 属性中设置的。

A big example with implementation can be found here: https://stackoverflow.com/a/68748914/13349759可以在这里找到一个重要的实施示例: https://stackoverflow.com/a/68748914/13349759

Here is a small snippet of XAML from another small example:这是来自另一个小示例的 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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