You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:36:19