Nicolas
Nicolas

Reputation: 19

INotifyDataErrorInfo - Red border doesn't disappear when error is cleared

Please check below a simple project reproducing the issue.

The issue is that the red border (meaning there is an error) remains on my control even though error is cleared.

In a WPF app, I have a "MainWindow", containing a "TestContainer" view (using a ContentControl), containing a "TestUserControl" view.

In the TestUserControlViewModel, when the OutsideString dependency property is set, I clear the error manually. But this is not taken into account and the red border remain --> NOK

If I add a button to the GUI to clear the error, then it works and the red border disappears.

I looks like a bug in the framework.

Do you have an idea ?

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="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <local:ConventionBasedDataTemplateSelector x:Key="ConventionBasedDataTemplateSelector"/>
    </Window.Resources>
    <Grid>
        <ContentControl Content="{Binding TestContainerViewModel}" ContentTemplateSelector="{StaticResource ConventionBasedDataTemplateSelector}" />
    </Grid>
</Window>

MainWindowViewModel.cs

namespace WpfApp1
{
    public class MainWindowViewModel
    {
        public TestContainerViewModel TestContainerViewModel { get; set; } = new();
    }
}

ConventionBasedDataTemplateSelector.cs

namespace WpfApp1
{
    public class ConventionBasedDataTemplateSelector : DataTemplateSelector
    {
        #region Public Methods
        /// <summary>
        /// When overridden in a derived class, returns a <see cref="T:System.Windows.DataTemplate" /> based on custom logic.
        /// </summary>
        /// <param name="item">The data object for which to select the template.</param>
        /// <param name="container">The data-bound object.</param>
        /// <returns>
        /// Returns a <see cref="T:System.Windows.DataTemplate" /> or null. The default value is null.
        /// </returns>
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            Type viewType = typeof(TestContainer);
            FrameworkElementFactory frameworkElementFactory = new(viewType);
            return new DataTemplate { VisualTree = frameworkElementFactory };
        }
        #endregion
    }
}

TestContainer.xaml

<UserControl x:Class="WpfApp1.TestContainer"
             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="450" d:DesignWidth="800"
             d:DataContext="{d:DesignInstance Type=local:TestContainerViewModel}">
    <Grid>
        <local:TestUserControl OutsideString="{Binding TotoString}"/>
    </Grid>
</UserControl>

TestContainerViewModel.cs

namespace WpfApp1
{
    public class TestContainerViewModel
    {
        public string TotoString => "Toto";
    }
}

TestUserControl.xaml

<UserControl x:Class="WpfApp1.TestUserControl"
             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="450" d:DesignWidth="800">
    <Grid Name="RootContainer">
        <Grid.DataContext>
            <local:TestUserControlViewModel/>
        </Grid.DataContext>
        <StackPanel>
            <TextBox Text="{Binding OutsideString}" Margin="10" />
        </StackPanel>
    </Grid>
</UserControl>

TestUserControl.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace WpfApp1
{
    public partial class TestUserControl : UserControl
    {
        public TestUserControl()
        {
            InitializeComponent();
        }

        #region OutsideString Dependency Property
        public static readonly DependencyProperty OutsideStringProperty = DependencyProperty.Register(
            nameof(OutsideString), typeof(string), typeof(TestUserControl),
            new PropertyMetadata(default(string), OnOutsideStringPropertyChanged));

        public string OutsideString
        {
            get => (string)GetValue(OutsideStringProperty);
            set => SetValue(OutsideStringProperty, value);
        }

        private static void OnOutsideStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TestUserControlViewModel viewModel = (TestUserControlViewModel)((TestUserControl)d).RootContainer.DataContext;
            viewModel.OutsideString = (string)e.NewValue;
        }
        #endregion
    }
}

TestUserControlViewModel.cs

using System.Collections;
using System.ComponentModel;

namespace WpfApp1
{

    public class TestUserControlViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
    {
        private string _outsideString;
        private bool _hasError = true;

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        
        public event PropertyChangedEventHandler PropertyChanged;

        public bool HasErrors => _hasError;
        
        public string OutsideString
        {
            get => _outsideString;
            set
            {
                _outsideString = value;
                _hasError = false;

                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OutsideString)));
                ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(OutsideString)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasErrors)));
            }
        }

        public IEnumerable GetErrors(string propertyName)
        {
            return HasErrors ? new []{"Error message"} : null;
        }

    }
}

Upvotes: 0

Views: 53

Answers (0)

Related Questions