So I have a WPF DataGrid
, which is bound to an ObservableCollection
. The collection has validation on its members, through IDataErrorInfo
. If I edit a cell in a way so as to be invalid, and then tab away from it before hitting enter, then come back and make it valid, the cell will stop showing invalid, however, the "!" at the head of the row will still be there, and the ToolTip
will reference the previous, invalid value.
问题:
回答1:
Not using Mode=TwoWay
for DataGridTextColumns
solves one version of the problem, however it seems that this problem can appear out of nowhere for other reasons as well.
(Anyone who has a good explanation as of why not using Mode=TwoWay
solves this in the first place is probably close to a solution to this problem)
The same thing just happened to me with a DataGridComboBoxColumn
so I tried to dig a little deeper.
The problem isn't the Binding
in the Control
that displays the ErrorTemplate
inside DataGridHeaderBorder
. It is binding its Visibility
to Validation.HasError
for the ancestor DataGridRow
(exactly as it should be doing) and that part is working.
Visibility="{Binding (Validation.HasError),
Converter={StaticResource bool2VisibilityConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"/>
The problem is that the validation error isn't cleared from the DataGridRow
once it is resolved. In my version of the problem, the DataGridRow
started out with 0 errors. When I entered an invalid value it got 1 error so, so far so good. But when I resolved the error it jumped up to 3 errors, all of which were the same.
Here I tried to resolve it with a DataTrigger
that set the ValidationErrorTemplate
to {x:Null}
if Validation.Errors.Count
wasn't 1. It worked great for the first iteration but once I cleared the error for the second time it was back. It didn't have 3 errors anymore, it had 7! After a couple of more iterations it was above 10.
I also tried to clear the errors manually by doing UpdateSource
and UpdateTarget
on the BindingExpressions
but no dice. Validation.ClearInvalid
didn't have any effect either. And looking through the source code in the Toolkit didn't get me anywhere :)
So I don't have any good solutions to this but I thought I should post my findings anyway..
My only "workaround" so far is to just hide the ErrorTemplate
in the DataGridRowHeader
<DataGrid ...>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
</Style>
</DataGrid.RowStyle>
<!-- ... -->
</DataGrid>
回答2:
I found best answer that worked for me. Just clear your DataGrid
's RowValidationErrorTemplate
.
In Code
YourGrid.RowValidationErrorTemplate = new ControlTemplate();
In Xaml
<DataGrid.RowValidationErrorTemplate> <ControlTemplate> </ControlTemplate> </DataGrid.RowValidationErrorTemplate>`
Then make your own Row Validation Error Template.
If your data item is INotifyPropertyChanged
((INotifyPropertyChanged)i).PropertyChanged += this.i_PropertyChanged;`
then
private void i_PropertyChanged(object sender, PropertyChangedEventArgs e) { this.Dispatcher.BeginInvoke(new Action(() => { var row = this.ItemContainerGenerator.ContainerFromItem(sender) as DataGridRow; if (row == null) return; var Errs = IsValid(row); if (Errs.Count == 0) row.Header = null; else { // Creatr error template var gg = new Grid { ToolTip = "Error Tooltip" }; var els = new Ellipse { Fill = new SolidColorBrush(Colors.Red), Width = row.FontSize, Height = row.FontSize }; var tb = new TextBlock { Text = "!", Foreground = new SolidColorBrush(Colors.White), HorizontalAlignment = HorizontalAlignment.Center, FontWeight = FontWeights.Bold }; gg.Children.Add(els); gg.Children.Add(tb); row.Header = gg; } }), System.Windows.Threading.DispatcherPriority.ApplicationIdle); }
Write your own IsValid method, the way you like
回答3:
My solution was to implement custom row validation feedback, similar to this page under the To customize row validation feedback section. The row error then disappears appropriately.
(I also added RowHeaderWidth="20"
to the DataGrid
definition, to avoid the table shift to the right the first time the exclamation point appears.)
回答4:
I have the same problem with the RowHeader error template not going away. I am using INotifyDataErrorInfo. Following up on the research by Fredrik Hedblad I have made a workaround; I have modified the DataGridRowHeader template to use a MultiBinding for the ValidationErrorTemplate visibility:
<Style x:Key="DataGridRowHeaderStyle" TargetType="{x:Type DataGridRowHeader}">
<!--<Setter Property="Background" Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=Brushes:BrushesLibrary1,
ResourceId=HeaderBrush}}"/>-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRowHeader}">
<Grid>
<Microsoft_Windows_Themes:DataGridHeaderBorder BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"
IsPressed="{TemplateBinding IsPressed}" IsHovered="{TemplateBinding IsMouseOver}"
IsSelected="{TemplateBinding IsRowSelected}" Orientation="Horizontal"
Padding="{TemplateBinding Padding}" SeparatorBrush="{TemplateBinding SeparatorBrush}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}">
<StackPanel Orientation="Horizontal">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"
Width="15"/>
<Control SnapsToDevicePixels="false"
Template="{Binding ValidationErrorTemplate, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}">
<Control.Visibility>
<MultiBinding Converter="{StaticResource ValidationConverter}">
<Binding Path="(Validation.HasError)" RelativeSource="{RelativeSource AncestorType={x:Type DataGridRow}}"/>
<Binding Path="DataContext.HasErrors" RelativeSource="{RelativeSource AncestorType={x:Type DataGridRow}}"/>
</MultiBinding>
</Control.Visibility>
<!-- Original binding below -->
<!--Visibility="{Binding (Validation.HasError), Converter={StaticResource bool2VisibilityConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}">-->
</Control>
</StackPanel>
</Microsoft_Windows_Themes:DataGridHeaderBorder>
<Thumb x:Name="PART_TopHeaderGripper" Style="{StaticResource RowHeaderGripperStyle}" VerticalAlignment="Top"/>
<Thumb x:Name="PART_BottomHeaderGripper" Style="{StaticResource RowHeaderGripperStyle}" VerticalAlignment="Bottom"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
This relies on the bound objects having a "HasErrors" property with change notification. In my project I have ensured that the HasErrors property is updated by raising the PropertyChanged for HasErrors in the item EndEdit event.
回答5:
try removing the Mode=TwoWay
for each of the DataGridTextColumns
from each of the Binding elements.
回答6:
If you are seeing an increasing number of errors similar to Meleak, I would be interested to know how your error collection gets populated. In Meleaks version of the problem, he sees three errors (and more) after resolving the invalid data.
In my Data Validation code, I remove the previous instance of a particular error then re-add every time the data changes. For reference, here is a sample:
The Validation Plumbing
#Region " Validation workers "
Private m_validationErrors As New Dictionary(Of String, String)
Private Sub AddError(ByVal ColName As String, ByVal Msg As String)
If Not m_validationErrors.ContainsKey(ColName) Then
m_validationErrors.Add(ColName, Msg)
End If
End Sub
Private Sub RemoveError(ByVal ColName As String)
If m_validationErrors.ContainsKey(ColName) Then
m_validationErrors.Remove(ColName)
End If
End Sub
Public ReadOnly Property [Error]() As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
If m_validationErrors.Count > 0 Then
Return "Shipment data is invalid"
Else
Return Nothing
End If
End Get
End Property
Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
If m_validationErrors.ContainsKey(columnName) Then
Return m_validationErrors(columnName).ToString
Else
Return Nothing
End If
End Get
End Property
#End Region
A Property Being Validated
Private Sub OnZIPChanged()
Me.RemoveError("ZIP")
If _ZIP Is Nothing OrElse _ZIP.Trim = "" Then
Me.AddError("ZIP", "Please enter a ZIP Code")
Else
Select Case _ZIP.Length
Case 5
Case 10
Case Else
Me.AddError("ZIP", "Please enter a ZIP Code")
End Select
End If
OnPropertyChanged("CanShip")
End Sub
So, when the property Changed handler is run, if an error exists in the ValidationErrors dictionary, it is removed, then the value is checked, and it if does not match requirements, an error is added to the dictionary. This helps ensure that only one instance of any error is present in that entities validation error dictionary.
回答7:
My workaround was not to use Validation.Errors, but use DataGridRow.Item property. If your DataGrid is bound to business objects which implement IDataErrorInfo interface, then you can add IsNotValid property (or IsValid), and make sure Error property returns all errors associated with the object. Then customize default style for DataGridRowHeader:
<Style x:Key="{x:Type DataGridRowHeader}" TargetType="{x:Type DataGridRowHeader}">
...
<Control SnapsToDevicePixels="false"
Visibility="{Binding RelativeSource={RelativeSource
AncestorType={x:Type DataGridRow}},
Path=Item.IsNotValid, Converter={StaticResource
Bool2VisibilityConverter}}"
Template="{Binding RelativeSource={RelativeSource
AncestorType={x:Type DataGridRow}},
Path=ValidationErrorTemplate}" />
...
</Style>
Also in DataGridRow style customize ValidationErrorTemplate, so that it shows error message from DataGridRow.Item.Error proeprty.
回答8:
In my case, it worked all well and good when we were using the DataGrid WPF3.5 version. We upgraded to 4.0, then it stopped resetting. After searches on SO, google etc, I chanced upon my solution. Setting UpdateSourceTrigger=PropertyChanged on the Binding in the DataGridTextColumn fixed it for me.
I just realised that the red exclamation mark does not clear on setting it to a correct value.
回答9:
My workaround was to simply remove the property UpdateSourceTrigger="LostFocus" from the binding declaration in each datagridcolumn.
回答10:
In my case I had to remove from binding definition
UpdateSourceTrigger=PropertyChanged
For me it works with both of these definitions:
<DataGridTextColumn
Header="Time, min"
x:Name="uiDataGridTextColumnTime"
Width="Auto"
CellStyle="{StaticResource ResourceKey=DataGridCellText}"
IsReadOnly="False">
<DataGridTextColumn.Binding>
<Binding Path="fTime" StringFormat="{}{0:0.00}">
<Binding.ValidationRules>
<Validation:CellDataInfoValidationRule ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
And
<DataGridTextColumn
Header="Time, min"
x:Name="uiDataGridTextColumnTime"
Width="Auto"
CellStyle="{StaticResource ResourceKey=DataGridCellText}"
Binding="{Binding fTime, StringFormat={}\{0:0.00\}, ValidatesOnDataErrors=True}"
IsReadOnly="False">
Validation:CellDataInfoValidationRule is custom class & get it here
public class CellDataInfoValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// obtain the bound business object
BindingExpression expression = value as BindingExpression;
IDataErrorInfo info = expression.DataItem as IDataErrorInfo;
// determine the binding path
string boundProperty = expression.ParentBinding.Path.Path;
// obtain any errors relating to this bound property
string error = info[boundProperty];
if (!string.IsNullOrEmpty(error))
{
return new ValidationResult(false, error);
}
return ValidationResult.ValidResult;
}
}
And your data object must implement IDataErrorInfo
回答11:
I am not using IDataErrorInfo
or INotifyDataErrorInfo
and my solution was to change my bindings from UpdateSourceTrigger="PropertyChanged"
to UpdateSourceTrigger="LostFocus"
This was the only thing that
If you are using ValidationRules in your DataGrid column defintion and you need the validation rules to run when ever the property changes (in the UI or the property) look into setting ValidatesOnTargetUpdated="True"
on your ValidationRule
XAML Example:
<DataGridTextColumn Header="Name"
CellStyle="{StaticResource DGCellStyle}"
ElementStyle="{StaticResource DGTextColValidationStyle}"
EditingElementStyle="{StaticResource DGTextColEditValidationStyle}">
<DataGridTextColumn.Binding>
<Binding Path="Name" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<ValidationResource:YourValidationRule ValidationStep="UpdatedValue" ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
回答12:
Ok, after some work an alteration of synergetic's solution worked for me, this is how I implemented it:
<Style x:Key="{x:Type DataGridRowHeader}" TargetType="{x:Type DataGridRowHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Control SnapsToDevicePixels="true"
Visibility="{Binding RelativeSource={RelativeSource
AncestorType={x:Type DataGridRow}},
Path=Item.HasErrors, Converter={StaticResource
BooleanToVisibilityConverter }}"
Template="{Binding RelativeSource={RelativeSource
AncestorType={x:Type DataGridRow}},
Path=ValidationErrorTemplate}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Don't forget to reference the BooleanToVisibilityConverter:
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
Just the row style does not (yet) look as good as the default one, I'm working on it.
Edit: Plz help here
回答13:
My scenario was like this:
- Model implements
IDataErrorInfo
Custom Row Validation rule based on WPF DataGrid Practical Examples -Validation with IDataErrorInfo , that combined all errors from Model using IDataErrorInfo.
<DataGrid.RowValidationRules> <local:RowDataInfoValidationRule ValidationStep="UpdatedValue" /> </DataGrid.RowValidationRules>
ValidatesOnDataErrors=True
,ValidatesOnExceptions=True
,NotifyOnValidationError=True
within the binding (which I started with)
This caused multiple access to my validation Engine and eventualy left my DataGrid
in inconsistent state (Error notification on row header even when row Valid).
The solution was to remove switches from the binding (point 3.)
I suggest reading through Clearing a DataGrid row validation error too.
回答14:
I have used this technique which do away with RowValidationRules and instead use the property validations in a viewmodel. This requires static variables and data annotations :
//uses Prism.MVVM for BindableBase and INotifyDataErrorInfo
private static int _xxStartNo;
private static int _xxEndNo;
// in property getter/setter
private int _startNo;
[CustomValidation(typeof(YourModel), "ValidateStartNoRange")]
public int StartNo
{
get
{
_xxStartNo=_startNo;
return _startNo;
}
set
{
..........
ValidateProperty("StartNo")
}
}
.......
public static ValidationResult ValidateStartNoRange(int number)
{
if(number > _xxEndNo)
{
return ValidationResult("Start No must be less than End No.";
}
return ValidationResult.Success;
}