Reputation: 19
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