du-it
du-it

Reputation: 3009

How can I display the sum of all cell's validation errors in a DataGridRow's ControlTemplate but only the cell's errors when hovering the cell?

I have a DataGrid and I validate the user input for each cell using IDataErrorInfo. An appropriate error message is displayed for each cell when the user inputs invalid data.
How can I collect and display all row's cell error messages when I hover the ValidationErrorTemplate (controltemplate) on the left hand side of the grid's row?

<Style TargetType="{x:Type DataGridRow}">
    <Setter Property="FontSize" Value="16"/>
    <Setter Property="FontFamily" Value="ArialMT"/>
    <Setter Property="Height" Value="24"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="ValidationErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <Grid>
                    <Ellipse Width="12" Height="12" Fill="Red" Stroke="Black" StrokeThickness="0.5"/>
                    <TextBlock FontWeight="Bold" Padding="4,0,0,0" Margin="0" VerticalAlignment="Top" Foreground="White" Text="!" />
                    <!--<ToolTip  {Binding RelativeSource={RelativeSourceSelf}, Path=(Validation.Errors)[0].ErrorContent}"/>-->
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
        <!--<DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}}" Value="true" >-->
        <!--<DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource  Self}}" Value="true" >-->
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="BorderBrush" Value="Red"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="IsEnabled" Value="True" />
        </Trigger>
        <Trigger Property="Validation.HasError" Value="false">
        <!--<DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}}" Value="false" >-->
        <!--<DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource  Self}}" Value="false" >-->
            <Setter Property="ToolTip" Value="{x:Null}"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="IsEnabled" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

Update 2019-01-25: My style defining an erroneous cell:

<Style x:Key="textBlockErrStyle" TargetType="{x:Type TextBlock}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource Self}}" Value="false" >
            <Setter Property="ToolTip" Value="{x:Null}"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding (Validation.HasError), RelativeSource={RelativeSource Self}}" Value="true" >
            <Setter Property="Background" Value="Red" />
            <Setter Property="Foreground" Value="White" />
            <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
            <!--<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=RawTag.ErrorList, Converter={StaticResource conv:StringToListConverter}}"/>-->
        </DataTrigger>
    </Style.Triggers>
</Style>

Update 2019-02-18:

This is how the DataGridRow looks like after an invalid character was entered: enter image description here

This is how the DataGridRow looks like after all invalid characters were deleted: enter image description here

--> The red border is still around the row and the alert sign on the left hand side of the row is still visible.

I tried this as well:

    public void RemoveError(string propertyName, bool notify = true)
    {
        if (ErrorList.ContainsKey(propertyName))
        {
            ErrorList.Remove(propertyName);
            HasErrors = ErrorList.Count > 0;
            //NotifyErrorsChanged(propertyName);
            OnPropertyChanged(propertyName);
            OnPropertyChanged("GetAllErrors");
        }
        if (notify) ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

...although I use OnPropertyChange anyway in each setter of th model class. The error list is empty but the border and the alert symbol stay.

Update 2019-02-19:

My implementation of the INotfiyPropertyChanged interface:

    #region INotifyPropertyChanged

    /// <summary>
    /// Handler to register for property changed event
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Method to invoke the property change event
    /// </summary>
    /// <param name="propertyName">The name of the changed property</param>
    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

Upvotes: 0

Views: 131

Answers (1)

Neil B
Neil B

Reputation: 2224

I display the tool tip like this:

ToolTip="{Binding Path=/ErrorContent}"

Here is the entire template.

<ControlTemplate x:Key="ValidationTemplate">
    <Grid>
        <Border BorderBrush="red" BorderThickness="1" Background="#11FF0000" Opacity="0.5" IsHitTestVisible="False" x:Name="errorBorder"/>
        <AdornedElementPlaceholder x:Name="placeholder" ToolTip="{Binding Path=/ErrorContent}" />
        <Ellipse DockPanel.Dock="Right"
                                ToolTip="{Binding Path=/ErrorContent}"
                                HorizontalAlignment="Left"
                                VerticalAlignment="Top"
                                Width="15" Height="15"
                                Margin="-15,0,0,0"
                                StrokeThickness="1" Fill="Red" >
            <Ellipse.Stroke>
                <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                    <GradientStop Color="#FFFA0404" Offset="0"/>
                    <GradientStop Color="#FFC9C7C7" Offset="1"/>
                </LinearGradientBrush>
            </Ellipse.Stroke>
        </Ellipse>
        <TextBlock Text="!" Foreground="White" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="-10,-1"
                                ToolTip="{Binding Path=/ErrorContent}" TextBlock.LineHeight="15.5"/>
    </Grid>
</ControlTemplate>

Looks like this: enter image description here

Edit: Info added per comment

If you want to display a list of all errors you will need to implement INotifyDataErrorInfo interface for your class. Then do something like this:

public bool HasErrors { get; set; } = false;
public Dictionary<string, string> ErrorList = new Dictionary<string, string>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void NotifyErrorsChanged(string PropertyName) { ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(PropertyName)); }

public IEnumerable GetErrors(string propertyName)
{
    if (!HasErrors || propertyName == null) return null;
    if (ErrorList.Keys.Contains(propertyName)) return new List<string>() { ErrorList[propertyName] };
    if (propertyName != "GetAllErrors") NotifyPropertyChanged("GetAllErrors");
    return new List<string>();
}

public string GetAllErrors
{
    get
    {
        if (!HasErrors) return null;
        int count = 1;
        var errors= "Errors:";
        foreach (var e in ErrorList)
        {
            errors += "\n" + count++ + ". " + e.Value;
        }
        return errors;
    }
}

Then in your RowStyle for the datagrid set the tooltip like this:

<DataGrid.RowStyle>
    <Style TargetType="{x:Type DataGridRow}">
        <Setter Property="ToolTip" Value="{Binding GetAllErrors}"/>
    </Style>
</DataGrid.RowStyle>

You should be able get a tooltip of all the errors by hovering any portion of the row.

enter image description here

EDIT 2: I added my own methods to remove errors.

public void RemoveError(string PropertyName, bool Notify = true)
{
    if (ErrorList.ContainsKey(PropertyName))
    {
        ErrorList.Remove(PropertyName);
        HasErrors = ErrorList.Count > 0;
    }
    if (Notify) ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(PropertyName));
}

public void ClearErrors()
{
    var removalList = new Dictionary<string, string>(ErrorList);
    ErrorList.Clear();
    HasErrors = false;
    foreach (var propertyName in removalList.Keys) ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}

Upvotes: 1

Related Questions