INotifyDataErrorInfo问题:错误清除后控件红色边框未消失
WPF验证错误边框未消失问题排查与解决
问题现象
- 当通过依赖属性
OutsideString设置值并手动清除INotifyDataErrorInfo错误后,TextBox的红色错误边框仍未消失 - 若通过GUI按钮触发清除错误操作,边框可正常消失
- 项目层级:
MainWindow→ 通过ContentControl加载TestContainer→TestContainer包含TestUserControl
复现项目代码
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 { public override DataTemplate SelectTemplate(object item, DependencyObject container) { Type viewType = typeof(TestContainer); FrameworkElementFactory frameworkElementFactory = new(viewType); return new DataTemplate { VisualTree = frameworkElementFactory }; } } }
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(); } 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; } } }
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; } } }
问题根源与解决思路
核心问题分析
GetErrors返回值不符合规范:WPF的INotifyDataErrorInfo要求无错误时返回空可枚举对象,而非null。返回null会导致验证系统无法正确识别错误已清除。- 绑定时序问题:TestUserControl的ViewModel在XAML中直接实例化,依赖属性回调触发时,TextBox的绑定可能未完成初始化,导致
ErrorsChanged事件未被验证系统正确捕获。 - 过时API的潜在问题:使用已废弃的
FrameworkElementFactory创建DataTemplate,可能导致视觉树绑定上下文或事件注册异常。
具体解决方案
方案1:修正GetErrors返回值
修改TestUserControlViewModel的GetErrors方法,返回空枚举替代null:
using System.Linq; public IEnumerable GetErrors(string propertyName) { return HasErrors ? new []{"Error message"} : Enumerable.Empty<string>(); }
方案2:调整事件触发时机
在依赖属性回调中延迟执行ViewModel更新,确保TextBox绑定初始化完成:
private static void OnOutsideStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TestUserControl control = (TestUserControl)d; TestUserControlViewModel viewModel = (TestUserControlViewModel)control.RootContainer.DataContext; // 延迟执行,保证绑定系统已初始化 control.Dispatcher.BeginInvoke(() => { viewModel.OutsideString = (string)e.NewValue; }); }
方案3:替换FrameworkElementFactory为XAML定义DataTemplate
在MainWindow.xaml的Resources中直接定义DataTemplate,移除自定义Selector:
<Window.Resources> <DataTemplate DataType="{x:Type local:TestContainerViewModel}"> <local:TestContainer/> </DataTemplate> </Window.Resources>
修改ContentControl,依赖WPF默认数据模板匹配:
<ContentControl Content="{Binding TestContainerViewModel}" />
同时删除ConventionBasedDataTemplateSelector类,此方式更稳定可靠。
方案4:统一ViewModel绑定方式
不在TestUserControl的XAML中直接实例化ViewModel,改为通过上级绑定传递:
- 修改
TestUserControl.xaml,移除Grid的DataContext:
<Grid Name="RootContainer"> <StackPanel> <TextBox Text="{Binding OutsideString}" Margin="10" /> </StackPanel> </Grid>
- 在
TestContainer.xaml中绑定TestUserControl的DataContext:
<local:TestUserControl OutsideString="{Binding TotoString}" DataContext="{Binding TestUserControlViewModel}"/>
- 在
TestContainerViewModel中添加ViewModel实例:
public class TestContainerViewModel { public string TotoString => "Toto"; public TestUserControlViewModel TestUserControlViewModel { get; set; } = new(); }
内容的提问来源于stack exchange,提问作者Nicolas
相关产品推荐
相关产品推荐

