验证错误模板显示在“ContentControl”而不是“TextBox”上

问题描述 投票:0回答:1

问题

我正在尝试针对验证

TextBox
输入控件的结果生成视觉反馈,满足以下要求:

  • 文本框边框周围显示一个红色框
  • 任何错误消息都会显示在文本框下方

相关的验证代码似乎正在工作:执行验证的函数正在运行,错误消息正在累积,

OnErrorsChanged
正在被调用,WPF正在做......某事......我的整个
周围出现一个红色框UserControl

在阅读了许多相关的SO问题/答案并考虑到正在绘制红色框的事实后,我最初的直觉是这与装饰器的放置有关。但是,我尝试在每一层放置

AdornerDecorator
,但没有效果。我还在运行时检查了文本框控件是否有可访问的装饰层,它们确实如此。它是从托管我的视图 (
ContentControl
) 的
View1.xaml
创建的。这似乎解释了为什么我在整个
ContentControl
周围看到一个红色框,但我很困惑为什么
TextBox
Validation.ErrorTemplate
没有控制装饰器的位置。

enter image description here

控制布局

  • View1.xaml
    • UserControl
      • Grid
        • GroupBox
          • StackPanel
            (垂直布局)
            • StackPanel
              (水平布局 - 对所有文本框控件重复)
              • Label
              • Textbox
                (已验证的控制 -
                ErrorTemplate
                此处)

代码

View1.xaml

<UserControl x:Class="View1"
             ....namespaces....
             xmlns:Fluent="urn:fluent-ribbon"
             xmlns:vm="clr-namespace:MyNamespace.ViewModel"
             xmlns:view="clr-namespace:MyNamespace.View"
             Name="MyView1"
             DataContext="{StaticResource g_MainWindowViewModel}"
             Validation.ErrorTemplate="{x:Null}">
<DockPanel LastChildFill="True">
    <ContentControl Name="NewSessionFormContentControl" 
                    Content="{Binding m_SessionFormViewModel}" 
                    DockPanel.Dock="Left"/>
</DockPanel>

ControlTemplate
取自BionicCode在这里的答案

<ControlTemplate x:Key="ValidationErrorTemplate">
    <DockPanel LastChildFill="True">
    <Border BorderBrush="Red" BorderThickness="1">
        <AdornedElementPlaceholder x:Name="AdornedElement"/>
    </Border>
    <Border Background="White" 
            BorderBrush="Red" 
            Padding="4"
            BorderThickness="1,0,1,1" 
            HorizontalAlignment="Left">
        <ItemsControl ItemsSource="{Binding}"
                        HorizontalAlignment="Left"
                        DockPanel.Dock="Right">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                <TextBlock Text="{Binding ErrorContent}" 
                            Foreground="Red"
                            DockPanel.Dock="Right"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Border>
    </DockPanel>
</ControlTemplate>

这是要在此视图中验证的示例输入控件:

<TextBox Name="Name1"
        Width="350" 
        Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
        Text="{Binding Name, Mode=OneWayToSource, ValidatesOnNotifyDataErrors=True,UpdateSourceTrigger=Explicit}"/>
wpf validation mvvm
1个回答
1
投票

您的代码有几个缺陷,并且它不遵守绑定引擎验证行为的某些细节。我还有一些建议可以改进您的整体代码。

1 缺少验证错误渲染
TextBox

正如我在评论中已经提到的,如果您希望绑定目标显示验证错误,则必须将

Binding.Mode
设置为
BindingMode.TwoWay

说明

如果只想简单的属性验证,那么

BindingMode.OneWayToSource
就足够了。绑定引擎集 附加属性,例如 上的
Validation.Errors
属性 目标元素。它在更新目标后执行此操作 财产。仅当
Binding.Mode
OneWay
OneTime
TwoWay
。因为你想触发 数据验证(需要更新绑定源), 唯一可行的模式是
TwoWay

修复

SessionFromView.xaml

Binding.Mode
绑定的
TextBox
设置为
BindingMode.TwoWay

<TextBox Name="SessionName"
         Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
         Text="{Binding Name, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}" />

2 ViewModelBase.ClearErrors 中的错误

您错过了在参数

INotifyDataErrorInfo.ErrorsChanged
propertyName
或空的情况下引发
null
事件。

说明

绑定引擎订阅

INotifyDataErrorInfo.ErrorsChanged
事件来更新绑定(基于配置的
Binding.Mode
)。如果您在不提供属性名称的情况下调用
ViewModelbase.ClearErrors
(如您所做的那样),那么清除错误不会影响 UI,因为永远不会触发绑定引擎来重新评估与事件关联的绑定。因此,一旦错误被清除,错误模板就不会被删除。

修复

ViewModelBase.cs

protected bool ClearErrors(string propertyName = "")
{
  if (string.IsNullOrEmpty(propertyName))
  {
    Errors.Clear();

    // FIX::Raise ErrrorsChanged event
    OnErrorsChanged(propertyName);

    return true;
  }

  if (Errors.Remove(propertyName))
  {
    OnErrorsChanged(propertyName);

    return true;
  }

  return false;
}

3
TextBox

父级周围的验证错误边界

您有三种可能的选择来解决此问题:

  1. 显式将
    Binding.ValidatesOnNotifyDataErrors
    设置为
    false
    (推荐)。
  2. Validation.ErrorTemplate
    设置为
    null
    (不推荐)。尽管我不推荐此解决方案(因为它仍然会使绑定忙于处理验证错误,从而引入开销和性能成本),但此解决方案更方便,因为您可以使用全局隐式样式将
    Validation.ErrorTemplate
    设置为
    {x:Null} 
    .
  3. 更改
    INotifyDataErrorInfo.GetErrors
    实现(不推荐)。这将消除获取绑定源的所有错误的可能性。 WPF 大量使用此功能(在您的情况下,这会导致
    UserControl
    周围出现红色边框(或者准确地说是父托管
    ContentControl
    )。

说明

Binding.ValidatesOnNotifyDataErrors
的默认值为
true
。此外,附加属性
Validation.ErrorTemplate
返回一个默认模板,该模板仅在目标元素周围绘制红色边框。

如果控件绑定到数据源对象而不是该对象上的属性,则绑定引擎会将空字符串而不是特定属性名称传递给

INotifyDataErrorInfo.GetErrors
方法。如果实现此方法以返回实例的所有当前错误,就像您的
ViewModelBase
所做的那样(这是推荐的实现),
那么目标就会显示错误:

<!-- 
  The Content property binding targets the whole data source object 
  and not a property of the data source. As a result, the binding engine can't query the errors 
  for a particular property and instead tries to get all errors of the source object.
  And because ValidatesOnNotifyDataErrors is TRUE by default, the binding on the Content property 
  will be validated.

  The following two bindings are equivalaent
-->

<ContentControl Name="NewSessionFormContentControl"
                Content="{Binding m_SessionFormViewModel}" />


<ContentControl Name="NewSessionFormContentControl"
                Content="{Binding m_SessionFormViewModel, ValidatesOnNotifyDataErrors=True}" />
// ViewModelBase: 
// This is were all errors of the source object are returned 
// in case the binding engine passes in an empty string
public System.Collections.IEnumerable GetErrors(string propertyName)
  => string.IsNullOrWhiteSpace(propertyName)
    ? Errors.SelectMany(entry => entry.Value) // Return all errors
    : Errors.TryGetValue(propertyName, out IList<object> errors)
      ? (IEnumerable<object>)errors
      : new List<object>();

修复#1(推荐)

SessionView.xaml

<!-- Disable validation for the particular binding and avoid related overhead -->
<ContentControl Name="NewSessionFormContentControl"
                Content="{Binding m_SessionFormViewModel, ValidatesOnNotifyDataErrors=False}" />

修复#2

SessionView.xaml

<!-- 
  Set Validation.ErrorTemplate to NULL. 
  The binding will still perform all the validation related operations
  but won't show the visual error feedback .
-->
<ContentControl Name="NewSessionFormContentControl"
                Validation.ErrorTemplate="{x:Null}"
                Content="{Binding m_SessionFormViewModel}" />

修复#3

ViewModelBase.xaml

// Don't support NULL and empty string queries
public System.Collections.IEnumerable GetErrors(string propertyName)
  => string.IsNullOrWhiteSpace(propertyName)
    ? new List<object>() // Return no errors
    : Errors.TryGetValue(propertyName, out IList<object> errors)
      ? (IEnumerable<object>)errors
      : new List<object>();
© www.soinside.com 2019 - 2024. All rights reserved.