在WPF 4.5中使用INotifyDataErrorInfo创建一个工具提示,显示控件的所有验证错误。

5
我有多个控件,包括一个文本框和一个组合框,我希望它们都能显示一条工具提示,其中包含在Validation.Errors集合中包含的所有错误。如果可能的话,我希望它们都共享一个公共样式,这就是我正在尝试的。我相信我在ToolTip setter中的绑定方面做错了什么,但我无法找出问题所在。在我的INotifyDataErrorInfo实现中返回一个错误对象,指定错误的严重程度(错误或警告)。
我想要一个适用于窗口中所有控件的样式,该样式将显示包含该控件的所有错误和警告列表的工具提示。错误应以红色显示,警告应以黄色显示。以下是我想出的样式:
        <Style TargetType="FrameworkElement">
        <Setter Property="ToolTip">
            <Setter.Value>
                <ItemsControl ItemsSource="{Binding Path=(Validation.Errors), RelativeSource={RelativeSource Self}}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding ErrorContent.ErrorMessage}">
                                <TextBlock.Style>
                                    <Style TargetType="TextBlock">
                                        <Setter Property="Foreground" Value="Red"/>
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding ErrorContent.ErrorSeverity}"
                                                             Value="{x:Static local:ErrorType.Warning}">
                                                <Setter Property="Foreground" Value="Yellow"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                            </TextBlock>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=(Validation.HasError)}" Value="True">
                <Setter Property="ToolTip">
                    <Setter.Value>
                        <ItemsControl ItemsSource="{Binding Path=(Validation.Errors)}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding ErrorContent.ErrorMessage}">
                                        <TextBlock.Style>
                                            <Style TargetType="TextBlock">
                                                <Setter Property="Foreground" Value="Red"/>
                                                <Style.Triggers>
                                                    <DataTrigger Binding="{Binding ErrorContent.ErrorSeverity}"
                                                             Value="{x:Static local:ErrorType.Warning}">
                                                        <Setter Property="Foreground" Value="Yellow"/>
                                                    </DataTrigger>
                                                </Style.Triggers>
                                            </Style>
                                        </TextBlock.Style>
                                    </TextBlock>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>

我尝试将RelativeSource更改为搜索AncestorLevel 1和2的控件类型,但似乎都不起作用。我基于一个ControlTemplate创建了样式,该样式与我想要在控件本身上显示的ToolTip完全相同:它根据错误严重性显示红色或黄色边框,并显示一个ToolTip。我确定这与我的绑定有关,因为ErrorTemplate自动将其DataContext设置为Validation.Errors集合,这使得很容易将ItemsSource绑定到ItemsCollection。但是样式的ToolTip没有这种运气。这是我用于我的ErrorTemplate的工作ControlTemplate:
        <ControlTemplate x:Key="ErrorTemplate">
        <Border BorderThickness="1">
            <AdornedElementPlaceholder Name="ElementPlaceholder"/>
            <Border.Style>
                <Style TargetType="Border">
                    <Setter Property="BorderBrush" Value="Red"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ElementName=ElementPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent.ErrorSeverity}"
                                         Value="{x:Static local:ErrorType.Warning}">
                            <Setter Property="BorderBrush" Value="Yellow"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
            <Border.ToolTip>
                <ItemsControl ItemsSource="{Binding}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding ErrorContent.ErrorMessage}">
                                <TextBlock.Style>
                                    <Style TargetType="TextBlock">
                                        <Setter Property="Foreground" Value="Red"/>
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding ErrorContent.ErrorSeverity}"
                                                             Value="{x:Static local:ErrorType.Warning}">
                                                <Setter Property="Foreground" Value="Yellow"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                            </TextBlock>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Border.ToolTip>
        </Border>
    </ControlTemplate>

有人能给我一些建议吗?

3个回答

9
这可以更轻松地实现。 如果您按照上面的方式编写 "Tooltip" 的绑定:
<Trigger Property="Validation.HasError" Value="True">
      <Setter Property="ToolTip"
              Value="{Binding RelativeSource={RelativeSource Self},  Path=(Validation.Errors), Converter={StaticResource ErrorCollectionConverter}}">
      </Setter>
</Trigger>

Binding(绑定)“奇迹般地”重新绑定到工具提示的“PlacementTarget”,因此绑定到了所附加的控件。

如果您需要显示完整的项目列表,可以执行以下操作:

<Trigger Property="Validation.HasError" Value="True">
      <Setter Property="ToolTip">
          <Setter.Value>
               <ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
                   <ItemsControl ItemsSource="{Binding Path=(Validation.Errors)}" DisplayMemberPath="ErrorContent" />
               </ToolTip>
          </Setter.Value>
      </Setter>
</Trigger>

你甚至可以放弃Tooltip对象,直接从ItemsControl绑定到PlacementTarget。然后只需通过ItemsControl上的AncestorType将Tooltip用作RelativeSource即可。
希望这能帮到你 :)

5

我试图解决这个问题已经有一段时间了,最终在MSDN论坛上的一篇帖子上找到了正确的方法。首先,我需要为每个目标类型指定样式,并以原始样式为基础进行设计。其次,我意识到问题不在于我的绑定,而在于绑定的集合没有被更新。我不知道为什么我的ListBox/ItemsControl在XAML中指定时不能被更新,但如果你在转换器中指定它,它确实可以工作。下面是我的新样式:

        <Style TargetType="Control" x:Key="ErrorToolTip">
        <Style.Resources>
            <Style TargetType="ListBoxItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <TextBlock Text="{Binding ErrorContent.ErrorMessage}"
                                       Background="Transparent">
                                <TextBlock.Style>
                                    <Style TargetType="TextBlock">
                                        <Setter Property="Foreground" Value="Red"/>
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding ErrorContent.ErrorSeverity}"
                                                         Value="{x:Static local:ErrorType.Warning}">
                                                <Setter Property="Foreground" Value="Orange" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                            </TextBlock>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Style.Resources>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors), Converter={StaticResource ErrorCollectionConverter}}">
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style TargetType="TextBox" BasedOn="{StaticResource ErrorToolTip}"/>
    <Style TargetType="ComboBox" BasedOn="{StaticResource ErrorToolTip}"/>

这是我的转换函数:

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;
        return new ListBox
        {
            ItemsSource = (ReadOnlyObservableCollection<ValidationError>) value,
            BorderThickness = new Thickness(0),
            Background = Brushes.Transparent
        };
    }

我希望这对于其他遇到和我一样问题的人有所帮助。如果有人知道为什么这会产生如此大的差异,我很想知道。


请注意,此代码需要使用自定义的ErrorContent对象,并在其上定义了ErrorMessage和ErrorSeverity属性。请参阅https://social.technet.microsoft.com/wiki/contents/articles/19490.wpf-4-5-validating-data-in-using-the-inotifydataerrorinfo-interface.aspx中的“自定义错误对象”部分。 - Scott Howard

2
这是我使用的Matts答案的简化版本,仅适用于类型为TextBox的控件,并且不使用错误严重性。我用它来显示用户输入的目录路径上下文中的一个或多个错误。与Matts的答案相比,我使用了 DisplayMemberPath = "ErrorContent" 直接访问转换器中的错误。
一个用于TextBox的样式,在附加属性Validation.HasError为true时显示工具提示:
<UserControl.Resources>
    <ui:ErrorCollectionConverter x:Key="ErrorCollectionConverter"></ui:ErrorCollectionConverter>
    <Style TargetType="TextBox">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip"
                    Value="{Binding RelativeSource={RelativeSource Self},  Path=(Validation.Errors), Converter={StaticResource ErrorCollectionConverter}}">
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>
</UserControl.Resources>

我的“目录”文本框隐式使用了样式:

<TextBox Text="{Binding Directory, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"></TextBox>

值转换器直接访问 ErrorContent 属性:
internal class ErrorCollectionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;
        return new ListBox
        {
            ItemsSource = (ReadOnlyObservableCollection<ValidationError>)value,
            BorderThickness = new Thickness(0),
            Background = Brushes.Transparent,
            DisplayMemberPath = "ErrorContent"
        };
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}   

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接