Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a ToolTip that shows all Validation.Errors for a control using INotifyDataErrorInfo in WPF 4.5

I have multiple controls including a TextBox and a ComboBox and I would like all of them to display a ToolTip with all of the errors contained in the Validation.Errors collection. I would like them all to share a common style if possible, which is what I am trying. I am convinced I am doing something wrong with my binding in the ToolTip setter, but I can't figure out what. I return an Error object in my INotifyDataErrorInfo implementation that specifies the severity of an error (Error or Warning).

I would like to have a style that applies to all controls in the Window that would display a ToolTip containing a list of all errors and warnings for that control. The errors should be displayed in red and the warnings in yellow. Here is the Style I have come up with:

        <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>

I have tried changing the RelativeSource to search for an AncestoryType of Control at both AncestorLevel 1 and 2. None of this seems to work.

I based the style on a ControlTemplate that I used for the ErrorTemplate that does pretty much the same thing: it displays a red or yellow border depending on the error severity and displays a ToolTip exactly like what I want to do for the ToolTip on the control itself. I'm certain it has something to do with my binding, because the ErrorTemplate automatically has its DataContext set to the Validation.Errors collection, which makes it easy to bind the ItemsSource for the ItmesCollection. The ToolTip for the style has no such luck. Here is the working ControlTemplate I used for my ErrorTemplate:

        <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>

Can anyone give me any suggestions?

like image 582
Matt Zappitello Avatar asked Apr 07 '14 12:04

Matt Zappitello


1 Answers

This could be achieved much easier.

If you write the binding to "Tooltip" as above:

<Trigger Property="Validation.HasError" Value="True">
      <Setter Property="ToolTip"
              Value="{Binding RelativeSource={RelativeSource Self},  Path=(Validation.Errors), Converter={StaticResource ErrorCollectionConverter}}">
      </Setter>
</Trigger>

The Binding "miraculously" actually rebinds itself to the "PlacementTarget" of the tooltip. Thus to the control it is attached.

If you need to display the full list of items you can do the below:

<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>

You could even drop the Tooltip object and bind to the PlacementTarget directly from the ItemsControl. Then just use the Tooltip as the RelativeSource via AncestorType on the ItemsControl.

Hope this helps :)

like image 129
Adam Avatar answered Oct 02 '22 15:10

Adam