fss
fss

Reputation: 113

WPF: Show validation Tooltip in DataGridComboBoxColumn

I have a DataGrid with several ComboBox columns in it. The values are validated using the IDataErrorInfo interface in the ViewModel. A tooltip should show the validation error when hovering over the appropriate cell.

The normal approach to achieve this behavior, is to use use an ElementStyle like the following:

<DataGridComboBoxColumn ...>
  <DataGridComboBoxColumn.ElementStyle>
    <Style TargetType="ComboBox">
      ...
      <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
          <Setter Property="ToolTip"
                  Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>

This works for DataGridTextColumns but not for DataGridComboBoxColumns (no tooltip is shown). If the same code is used for the EditingElementStyle everything works just fine. But only if the cell is in edit mode (as expected, because EditingElementStyle is only used in edit mode).

The solutions for this problem found in the WWW are recommending to use a CellStyle like this:

<DataGridComboBoxColumn ...>
  <DataGridComboBoxColumn.CellStyle>
    <Style TargetType="DataGridCell">
      <Style.Triggers>
        <DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}" 
                     Value="True">
          <Setter Property="ToolTip"
                            Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent}" />
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </DataGridComboBoxColumn.CellStyle>
</DataGridComboBoxColumn>

This approach uses the DataGridRow and a hard coded index to get the errors. This seems very error-prone to me. Anyway, it should work as long as there is only a single validation error in the row. But as soon as there are two or more validation errors in the same row, it doesn't work anymore. Every tooltip shows the first validation error (because of the hard coded index):

Tooltips

So it seems naturally for me to use the DataGridCell instead of the DataGridRow to get the errors (see RelativeSource in the bindings below):

<DataGridComboBoxColumn ...>
  <DataGridComboBoxColumn.CellStyle>
    <Style TargetType="DataGridCell">
      <Style.Triggers>
        <DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource Self}}" 
                     Value="True">
          <Setter Property="ToolTip"
                            Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </DataGridComboBoxColumn.CellStyle>
</DataGridComboBoxColumn>

But using this style, no Tooltip is shown. There are no errors or warnings in the output window. Adding PresentationTraceSources.TraceLevel=High to the trigger binding shows, that the HasError property returns false.

When removing the trigger around the Tooltip binding, Validation.Errors seems not to contain any values:

System.Windows.Data Warning: 56 : Created BindingExpression (hash=13069983) for Binding (hash=50848483)
System.Windows.Data Warning: 58 :   Path: '(0)[0].ErrorContent'
System.Windows.Data Warning: 60 : BindingExpression (hash=13069983): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=13069983): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=13069983): Attach to System.Windows.Controls.DataGridCell.ToolTip (hash=21168757)
System.Windows.Data Warning: 67 : BindingExpression (hash=13069983): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=13069983): Found data context element: <null> (OK)
System.Windows.Data Warning: 72 :   RelativeSource.Self found DataGridCell (hash=21168757)
System.Windows.Data Warning: 78 : BindingExpression (hash=13069983): Activate with root item DataGridCell (hash=21168757)
System.Windows.Data Warning: 108 : BindingExpression (hash=13069983):   At level 0 - for DataGridCell.(Validation.Errors) found accessor DependencyProperty(Errors)
System.Windows.Data Warning: 104 : BindingExpression (hash=13069983): Replace item at level 0 with DataGridCell (hash=21168757), using accessor DependencyProperty(Errors)
System.Windows.Data Warning: 101 : BindingExpression (hash=13069983): GetValue at level 0 from DataGridCell (hash=21168757) using DependencyProperty(Errors): ReadOnlyObservableCollection`1 (hash=51278326 Count=0)
System.Windows.Data Warning: 109 : BindingExpression (hash=13069983):   At level 1 - for ReadOnlyObservableCollection`1[] found accessor RuntimePropertyInfo(Item)
System.Windows.Data Warning: 104 : BindingExpression (hash=13069983): Replace item at level 1 with ReadOnlyObservableCollection`1 (hash=51278326 Count=0), using accessor RuntimePropertyInfo(Item)
System.Windows.Data Warning: 101 : BindingExpression (hash=13069983): GetValue at level 1 from ReadOnlyObservableCollection`1 (hash=51278326 Count=0) using RuntimePropertyInfo(Item): {IListIndexOutOfRange}
System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0)[0].ErrorContent; DataItem='DataGridCell' (Name=''); target element is 'DataGridCell' (Name=''); target property is 'ToolTip' (type 'Object') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Das angegebene Argument liegt außerhalb des gültigen Wertebereichs.
Parametername: index'
System.Windows.Data Warning: 103 : BindingExpression (hash=13069983): Replace item at level 2 with {NullDataItem}
System.Windows.Data Warning: 80 : BindingExpression (hash=13069983): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 88 : BindingExpression (hash=13069983): TransferValue - using fallback/default value <null>
System.Windows.Data Warning: 89 : BindingExpression (hash=13069983): TransferValue - using final value <null>

Does somebody has a solution for this problem?

Upvotes: 4

Views: 1392

Answers (1)

mm8
mm8

Reputation: 169160

Does somebody has a solution for this problem?

Why don't you simply replace the DataGridComboBoxColumn with a DataGridTemplateColumn and use a TextBlock in the CellTemplate and a ComboBox in the CellEditingTemplate?

Much more flexible and the exact same results. And on top of that it actually solves your issue:

<DataGridTemplateColumn Header="...">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name, ValidatesOnDataErrors=True}">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Style.Triggers>
                            <Trigger Property="Validation.HasError" Value="true">
                                <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding SomeItems, RelativeSource={RelativeSource AncestorType=Window}}"
                                      SelectedItem="{Binding Name, ValidatesOnDataErrors=True}">
                <ComboBox.Style>
                    <Style TargetType="ComboBox">
                        <Style.Triggers>
                            <Trigger Property="Validation.HasError" Value="true">
                                <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </ComboBox.Style>
            </ComboBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

When a DataGridComboBoxColumn is in read-only mode, there is no "real" ComboBox displayed.

Upvotes: 2

Related Questions