所以我有一个WPF DataGrid
,它绑定到ObservableCollection
。该集合通过IDataErrorInfo
对其成员进行了验证。如果我以某种方式编辑单元格以使其无效,然后在击中输入之前将其标记离开它,然后返回并使其有效,单元格将停止显示无效,但是,“!”行的开头仍然在那里,ToolTip
将引用前一个无效值。
答案 0 :(得分:22)
不使用Mode=TwoWay
DataGridTextColumns
来解决问题的一个版本,但是由于其他原因,这个问题似乎无处不在。
(对于为什么不使用Mode=TwoWay
解释这一问题的人有一个很好的解释,可能接近这个问题的解决方案)
同样的事情发生在我身上DataGridComboBoxColumn
所以我试图深入挖掘一下。
问题不在于显示Binding
Control
ErrorTemplate
的{{1}} DataGridHeaderBorder
。它将Visibility
与Validation.HasError
绑定到祖先DataGridRow
(正如它应该做的那样)并且该部分正在运行。
Visibility="{Binding (Validation.HasError),
Converter={StaticResource bool2VisibilityConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"/>
问题是验证错误在解析后不会从DataGridRow
中清除。在我的问题版本中,DataGridRow
以0错误开始。当我输入一个无效值时,它得到1个错误,所以到目前为止一切都很好。但是当我解决错误时,它跳到了3个错误,所有错误都相同。
我尝试使用DataTrigger
解决此问题,如果ValidationErrorTemplate
不是1,则将{x:Null}
设置为Validation.Errors.Count
。它在第一次迭代时效果很好但是一次我第二次清除了错误。它不再有3个错误,它有7个!经过几次迭代后,它超过了10。
我还尝试通过UpdateSource
上的UpdateTarget
和BindingExpressions
手动清除错误,但没有骰子。 Validation.ClearInvalid
也没有任何影响。查看工具包中的源代码并没有让我随处可见:)
所以我没有任何好的解决方案,但我认为我应该发布我的发现..
到目前为止我唯一的“解决方法”是隐藏ErrorTemplate
中的DataGridRowHeader
<DataGrid ...>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
</Style>
</DataGrid.RowStyle>
<!-- ... -->
</DataGrid>
答案 1 :(得分:5)
我找到了最适合我的答案。只需清除DataGrid
的{{1}}。
在代码中
RowValidationErrorTemplate
在Xaml
YourGrid.RowValidationErrorTemplate = new ControlTemplate();
然后制作您自己的行验证错误模板。
如果您的数据项是 INotifyPropertyChanged
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>`
然后
((INotifyPropertyChanged)i).PropertyChanged += this.i_PropertyChanged;`
按您喜欢的方式编写自己的IsValid方法
答案 2 :(得分:2)
我的解决方案是实现自定义行验证反馈,类似于 自定义行验证反馈 部分下的this page。行错误然后适当地消失。
(我还在RowHeaderWidth="20"
定义中添加了DataGrid
,以避免在感叹号第一次出现时向右移动表。)
答案 3 :(得分:2)
我遇到的问题是RowHeader错误模板不会消失。我正在使用INotifyDataErrorInfo。继Fredrik Hedblad的研究之后,我做了一个解决方法;我修改了DataGridRowHeader模板以使用MultiBinding来实现ValidationErrorTemplate可见性:
<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>
这取决于具有&#34; HasErrors&#34;的绑定对象。带变更通知的财产。在我的项目中,我确保通过在EndEdit项事件中为HasErrors引发PropertyChanged来更新HasErrors属性。
答案 4 :(得分:2)
我找到了此问题的根本原因。这与BindingExpressionBase
失去对BindingGroup
的引用的方式有关,因为只有BindingExpression
才有责任删除其ValidationErrors
。
在DataGrid验证的情况下,它有多个来源可能会丢失参考:
DataGridCell
为DataGridCell.BuildVisualTree()
重建时,属于该单元格的BindingExpressions
中所有旧的BindingGroup
被删除,然后Content
属性更改为新值Content
的{{1}}属性被更改(通过DataGridCell
或其他方式)时,旧版本上的所有绑定都将调用DataGridCell.BuildVisualTree()
方法属性值,它也将在删除任何BindingExpressionBase.Detach()
之前删除对BindingGroup
的引用ValidationError
的所有引用实际上都是BindingExpressionBase
,即使上述所有情况都不会导致删除引用,而是在{{1 }}(WeakReference
)中,基础TargetElement
有可能返回BindingExpressionBase
,并且属性访问器再次调用损坏的WeakReference
方法有了上述发现,现在也很清楚为什么不对于null
使用Detach()
有时可以解决该问题。 Mode=TwoWay
将变为只读状态,因此DataGridTextColumn
的{{1}}属性将永远不会更改。
为此,我使用了附件的DataGridTextColumn
编写了解决方法。
Content
然后将此属性与绑定绑定到DataGridCell
的{{1}}属性。
DependencyProperty
答案 5 :(得分:1)
尝试从每个Binding元素中删除每个Mode=TwoWay
的{{1}}。
答案 6 :(得分:1)
如果您看到越来越多的错误类似于Meleak,我很想知道您的错误集合是如何填充的。在Meleaks版本的问题中,他在解析无效数据后会看到三个错误(等等)。
在我的数据验证代码中,我删除了特定错误的先前实例,然后在每次数据更改时重新添加。作为参考,这是一个示例:
#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
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
因此,当运行属性Changed处理程序时,如果ValidationErrors字典中存在错误,则将其删除,然后检查该值,如果与要求不匹配,则会向字典添加错误。这有助于确保该实体验证错误字典中只存在任何错误的一个实例。
答案 7 :(得分:1)
我的解决方法是不使用Validation.Errors,而是使用DataGridRow.Item属性。如果DataGrid绑定到实现IDataErrorInfo接口的业务对象,则可以添加IsNotValid属性(或IsValid),并确保Error属性返回与该对象关联的所有错误。然后为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>
同样在DataGridRow样式中自定义ValidationErrorTemplate,以便它显示来自DataGridRow.Item.Error proeprty的错误消息。
答案 8 :(得分:1)
就我而言,当我们使用DataGrid WPF3.5版本时,它运行良好。我们升级到4.0,然后停止重置。在搜索了SO,google等之后,我偶然发现了我的解决方案。 在DataGridTextColumn的Binding上设置 UpdateSourceTrigger = PropertyChanged 为我修复了它。
我刚刚意识到红色感叹号在设置为正确值时无法清除。
答案 9 :(得分:0)
我的情景是这样的:
IDataErrorInfo
基于WPF DataGrid Practical Examples -Validation with IDataErrorInfo的自定义行验证规则,它使用IDataErrorInfo组合了Model中的所有错误。
<DataGrid.RowValidationRules>
<local:RowDataInfoValidationRule ValidationStep="UpdatedValue" />
</DataGrid.RowValidationRules>
ValidatesOnDataErrors=True
,ValidatesOnExceptions=True
,NotifyOnValidationError=True
(我开始时)
这导致多次访问我的验证引擎并最终导致我的DataGrid
处于不一致状态(即使行有效,也会在行标题上显示错误通知)。
解决方案是从绑定中删除开关(第3点)。
答案 10 :(得分:0)
我的解决方法是从每个datagrid列中的绑定声明中删除属性 UpdateSourceTrigger =“LostFocus”。
答案 11 :(得分:0)
在我的情况下,我不得不从绑定定义中删除
UpdateSourceTrigger=PropertyChanged
对我而言,它适用于以下两种定义:
<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>
和
<DataGridTextColumn
Header="Time, min"
x:Name="uiDataGridTextColumnTime"
Width="Auto"
CellStyle="{StaticResource ResourceKey=DataGridCellText}"
Binding="{Binding fTime, StringFormat={}\{0:0.00\}, ValidatesOnDataErrors=True}"
IsReadOnly="False">
验证:CellDataInfoValidationRule是自定义类&amp;得到它
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;
}
}
您的数据对象必须实现IDataErrorInfo
答案 12 :(得分:0)
我没有使用IDataErrorInfo
或INotifyDataErrorInfo
,我的解决方案是将我的绑定从UpdateSourceTrigger="PropertyChanged"
更改为UpdateSourceTrigger="LostFocus"
这是唯一的
如果您在DataGrid列定义中使用ValidationRules,并且需要验证规则在属性更改时运行(在UI或属性中),请查看ValidatesOnTargetUpdated="True"
<上的设置ValidationRule
/ p>
XAML示例:
<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>
答案 13 :(得分:0)
好的,经过一些工作,改变了协同解决方案对我有用,这就是我实现它的方式:
<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>
不要忘记引用BooleanToVisibilityConverter:
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
只是行样式(还)看起来不如默认样式,我正在处理它。
编辑:Plz帮助
答案 14 :(得分:0)
我已经使用了这种取消RowValidationRules的技术,而是在viewmodel中使用属性验证。这需要静态变量和数据注释:
//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;
}
答案 15 :(得分:0)
最初的问题来自2011年,而Datagrids Validation-System仍然存在许多问题,因此无法使用。我花了2天的时间来寻找解决方案,以完成以下工作:
实现此行为的唯一方法是放弃RowValidation和CellValidation,而改用RowHeader和Styles。我从网络上的各种来源复制了以下代码。我尚未对此进行广泛的测试,但是乍看之下它看起来很有希望。
在DataGrids XAML中:
<DataGrid ... local:DataGridProps.ShowCellErrorBorder="False">
<DataGrid.Resources>
<local:DataGridValidationConverter x:Key="DataGridValidationConverter" />
<Style TargetType="TextBlock" x:Key="errTemplate">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding Path=(Validation.Errors)[0].ErrorContent}
RelativeSource={x:Static RelativeSource.Self}, "/>
<Setter Property="Background" Value="LightSalmon"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<Grid Margin="0,-2,0,-2"
Visibility="{Binding Path=DataContext.HasErrors,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}},
Converter={StaticResource DataGridValidationConverter},
FallbackValue=Hidden}">
<Ellipse StrokeThickness="0" Fill="Red"
Width="{Binding Path=FontSize,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}}"
Height="{Binding Path=FontSize,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}}" />
<TextBlock Text="!" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"
FontSize="{Binding Path=FontSize,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}}" />
</Grid>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="Vorname" ElementStyle="{StaticResource errTemplate}"
Binding="{Binding Path=Vorname,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}" />
...
</DataGrid.Columns>
</DataGrid>
DataGridValidationConverter:
public class DataGridValidationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
DataGridProps:
public class DataGridProps
{
public static readonly DependencyProperty ShowCellErrorBorderProperty = DependencyProperty.RegisterAttached(
"ShowCellErrorBorder", typeof(bool), typeof(DataGridProps), new PropertyMetadata(true, ShowCellErrorBorderPropertyChangedCallback));
public static bool GetShowCellErrorBorder(DependencyObject element)
{
return (bool)element.GetValue(ShowCellErrorBorderProperty);
}
public static void SetShowCellErrorBorder(DependencyObject element, bool value)
{
element.SetValue(ShowCellErrorBorderProperty, value);
}
private static void ShowCellErrorBorderPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (GetShowCellErrorBorder(dependencyObject)) return;
var dg = dependencyObject as DataGrid;
if (null != dg)
{
dg.Loaded += (sender, args) =>
{
var scrollView = dg.Template.FindName("DG_ScrollViewer", dg) as ScrollViewer;
if (null == scrollView) return;
var scrollContent = scrollView.Template.FindName("PART_ScrollContentPresenter", scrollView) as ScrollContentPresenter;
if (null == scrollContent) return;
scrollContent.AdornerLayer.Visibility = Visibility.Hidden;
};
}
}
}
模型的实现:
public Model()
{
if (Vorname == null)
Vorname = "";
...
errorsByPropertyName = new Dictionary<string, List<string>>();
this.PropertyChanged += Model_PropertyChanged;
ForceRevalidation(null);
}
private string _vorname;
public string Vorname { get => _vorname; set => SetField(ref _vorname, value); }
...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private Dictionary<string, List<string>> errorsByPropertyName;
public void ForceRevalidation(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
foreach (PropertyInfo property in GetType().GetProperties())
{
ValidateProperty(property.Name);
}
}
else
{
ValidateProperty(propertyName);
}
}
protected virtual void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
OnPropertyChanged(nameof(HasErrors));
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => errorsByPropertyName.Any();
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (propertyName == null)
propertyName = "";
return errorsByPropertyName.ContainsKey(propertyName) ? errorsByPropertyName[propertyName] : null;
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
ValidateProperty(e.PropertyName);
}
protected virtual void ValidateProperty(string propertyName)
{
if (propertyName == null)
propertyName = "";
ClearErrors(propertyName);
switch (propertyName)
{
case nameof(Vorname):
if (string.IsNullOrWhiteSpace(Vorname))
AddError(propertyName, propertyName + " is empty");
break;
...
default:
break;
}
}
protected void AddError(string propertyName, string error)
{
if (!errorsByPropertyName.ContainsKey(propertyName))
errorsByPropertyName[propertyName] = new List<string>();
if (!errorsByPropertyName[propertyName].Contains(error))
{
errorsByPropertyName[propertyName].Add(error);
OnErrorsChanged(propertyName);
}
}
protected void ClearErrors(string propertyName)
{
if (errorsByPropertyName.ContainsKey(propertyName))
{
errorsByPropertyName.Remove(propertyName);
OnErrorsChanged(propertyName);
}
}
我从我的更大的Model-Base-Class创建了这个最小的示例,希望在这里我对这方面有所有重要的东西。