WPF中如何修改AdornerLayer以显示RangeBase派生控件的ValidationError
我来帮你解决这个问题——我之前也碰到过类似的场景,WPF的验证系统默认是元素级别的,所以要让派生的RangeBase控件也显示错误装饰,得把内部TextBox的错误信息传递给控件本身,或者调整绑定的验证逻辑。以下两种方案都能解决你的问题:
方案一:将TextBox的验证错误传递给派生控件
这种方法适合TextBox的验证规则独立于Value属性的场景,核心是让派生控件实现INotifyDataErrorInfo接口,把内部TextBox的错误暴露给控件本身,从而触发控件级别的验证装饰。
步骤1:让派生控件实现INotifyDataErrorInfo
在你的RangeBase派生类中添加接口实现,用来管理和暴露错误信息:
public class CustomRangeControl : RangeBase, INotifyDataErrorInfo { // 存储错误信息,键为属性名(这里我们关联Value属性) private readonly Dictionary<string, List<string>> _errors = new(); public bool HasErrors => _errors.Any(); public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged; public IEnumerable GetErrors(string? propertyName) { if (string.IsNullOrEmpty(propertyName) || !_errors.ContainsKey(propertyName)) return Enumerable.Empty<string>(); return _errors[propertyName]; } private void OnErrorsChanged(string propertyName) { ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); } }
步骤2:监听TextBox的验证错误事件
重写OnApplyTemplate方法,找到模板内的TextBox并订阅它的Validation.Error事件,把错误同步到控件的错误集合中:
public override void OnApplyTemplate() { base.OnApplyTemplate(); // 替换成你模板中TextBox的实际命名(通常用PART_前缀) var textBox = GetTemplateChild("PART_InputTextBox") as TextBox; if (textBox != null) { textBox.ValidationError += TextBox_ValidationError; // 记得在控件卸载时取消订阅,避免内存泄漏 textBox.Unloaded += (s, e) => textBox.ValidationError -= TextBox_ValidationError; } } private void TextBox_ValidationError(object sender, ValidationErrorEventArgs e) { const string targetProperty = nameof(Value); if (e.Action == ValidationErrorEventAction.Added) { if (!_errors.ContainsKey(targetProperty)) _errors[targetProperty] = new List<string>(); _errors[targetProperty].Add(e.Error.ErrorContent.ToString()); } else { if (_errors.TryGetValue(targetProperty, out var errorList)) { errorList.Remove(e.Error.ErrorContent.ToString()); if (errorList.Count == 0) _errors.Remove(targetProperty); } } // 通知错误状态变化 OnErrorsChanged(targetProperty); }
步骤3:确保控件有验证装饰模板
如果你的控件用了自定义模板,需要给它设置Validation.ErrorTemplate,确保错误提示能显示出来:
<Style TargetType="{x:Type local:CustomRangeControl}"> <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <DockPanel> <!-- 底部显示错误文本 --> <TextBlock Foreground="Red" FontSize="12" Text="{Binding [0].ErrorContent}" DockPanel.Dock="Bottom"/> <!-- 占位符,指向要装饰的控件本身 --> <AdornedElementPlaceholder/> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> <!-- 其他样式和模板设置 --> </Style>
方案二:直接给Value属性的绑定添加验证规则
如果TextBox的Text是双向绑定到Value属性,那可以把验证规则直接加到Value的绑定上,这样输入无效时,Value属性的绑定会触发错误,控件本身和TextBox都会显示验证装饰。
使用示例
在使用控件时,给Value的绑定添加验证规则:
<local:CustomRangeControl Value="{Binding MyViewModelValue, Mode=TwoWay, ValidatesOnDataErrors=True, ValidationRules={StaticResource NumberValidationRule}}"/>
然后模板内的TextBox直接绑定到控件的Value属性:
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
这种方法的前提是Value的类型和TextBox输入能正确转换,否则会触发类型转换错误,同样会显示验证提示。
关键注意事项
- 方案一中一定要在TextBox卸载时取消订阅
Validation.Error事件,避免内存泄漏。 - 自定义模板时,
AdornedElementPlaceholder是验证装饰的核心,不能省略。 - 如果你的验证规则是基于ViewModel的,确保ViewModel也实现了
INotifyDataErrorInfo或者IDataErrorInfo。
内容的提问来源于stack exchange,提问作者Stefan Babos
相关产品推荐
相关产品推荐

