Reputation: 3009
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:
This is how the DataGridRow looks like after all invalid characters were deleted:
--> 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
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>
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.
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