s73*_*v3r 36 validation wpf datagrid idataerrorinfo wpfdatagrid
所以我有一个WPF DataGrid,它必然会被绑定ObservableCollection.该集合通过对其成员进行验证IDataErrorInfo.如果我以某种方式编辑单元格以使其无效,然后在击中输入之前将其标记离开它,然后返回并使其有效,单元格将停止显示无效,但是,"!" 在行的头部仍将存在,并且ToolTip将引用先前的无效值.
Fre*_*lad 23
不Mode=TwoWay用于DataGridTextColumns解决问题的一个版本,但是由于其他原因,这个问题似乎无处不在.
(任何有一个很好的解释,为什么不使用Mode=TwoWay解决这个问题的人可能接近解决这个问题)
同样的事情发生在我身上,DataGridComboBoxColumn所以我试着深入挖掘一下.
问题是不是Binding在Control显示的ErrorTemplate内部DataGridHeaderBorder.它结合了Visibility对Validation.HasError始祖DataGridRow(正是因为它应该做的),而且部分工作.
Visibility="{Binding (Validation.HasError),
Converter={StaticResource bool2VisibilityConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"/>
Run Code Online (Sandbox Code Playgroud)
问题是验证错误DataGridRow一旦解决就不会被清除.在我的问题版本中,DataGridRow开始出现0错误.当我输入一个无效值时,它得到1个错误,所以到目前为止一切都很好.但是当我解决错误时,它跳到了3个错误,所有错误都相同.
在这里,我试图用一个来解决它DataTrigger是设置ValidationErrorTemplate到{x:Null},如果Validation.Errors.Count没有1.对于第一次迭代的工作很好,但一旦我清除错误,第二次就又回来了.它不再有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>
Run Code Online (Sandbox Code Playgroud)
我找到了最适合我的答案.请清楚你DataGrid的RowValidationErrorTemplate.
在代码中
YourGrid.RowValidationErrorTemplate = new ControlTemplate();
Run Code Online (Sandbox Code Playgroud)在Xaml
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>`
Run Code Online (Sandbox Code Playgroud)然后制作自己的行验证错误模板.
如果您的数据项是INotifyPropertyChanged
((INotifyPropertyChanged)i).PropertyChanged += this.i_PropertyChanged;`
Run Code Online (Sandbox Code Playgroud)
然后
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);
}
Run Code Online (Sandbox Code Playgroud)按照自己喜欢的方式编写自己的IsValid方法
我找到了这个问题的根本原因。这与如何BindingExpressionBases 失去对 的引用有关BindingGroup,因为只有BindingExpression负责删除其ValidationErrors.
在 DataGrid 验证的这种情况下,它有多个可能丢失引用的来源:
DataGridCell由DataGridCell.BuildVisualTree()所有旧BindingExpressions的BindingGroup属于该单元被删除,它的前Content属性更改为新的值Content属性DataGridCell被更改(通过DataGridCell.BuildVisualTree()或其他方式)时,该BindingExpressionBase.Detach()方法将被调用用于旧属性值上的所有绑定,这也会BindingGroup在任何ValidationError有机会被删除之前删除对 的引用BindingExpressionBase实际上都是WeakReferences,即使上述所有情况都不会导致引用的删除,但是当某些东西查找TargetElementof 时BindingExpressionBase,底层WeakReference返回null并且属性访问器有可能再次调用破碎的Detach()方法有了上述发现,现在也很清楚为什么不使用Mode=TwoWayforDataGridTextColumn有时可以解决问题。该DataGridTextColumn会成为只读和Content财产DataGridCell因此从来没有改变过。
我已经DependencyProperty为此使用附件编写了一个解决方法。
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
namespace Utilities
{
public static class DataGridExtension
{
/// <summary>
/// Identifies the FixBindingGroupValidationErrorsFor attached property.
/// </summary>
public static readonly DependencyProperty FixBindingGroupValidationErrorsForProperty =
DependencyProperty.RegisterAttached("FixBindingGroupValidationErrorsFor", typeof(DependencyObject), typeof(DataGridExtension),
new PropertyMetadata(null, new PropertyChangedCallback(OnFixBindingGroupValidationErrorsForChanged)));
/// <summary>
/// Gets the value of the FixBindingGroupValidationErrorsFor property
/// </summary>
public static DependencyObject GetFixBindingGroupValidationErrorsFor(DependencyObject obj)
{
return (DependencyObject)obj.GetValue(FixBindingGroupValidationErrorsForProperty);
}
/// <summary>
/// Sets the value of the FixBindingGroupValidationErrorsFor property
/// </summary>
public static void SetFixBindingGroupValidationErrorsFor(DependencyObject obj, DependencyObject value)
{
obj.SetValue(FixBindingGroupValidationErrorsForProperty, value);
}
/// <summary>
/// Handles property changed event for the FixBindingGroupValidationErrorsFor property.
/// </summary>
private static void OnFixBindingGroupValidationErrorsForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DependencyObject oldobj = (DependencyObject)e.OldValue;
if (oldobj != null)
{
BindingGroup group = FindBindingGroup(d); //if d!=DataGridCell, use (DependencyObject)e.NewValue
var leftOverErrors = group.ValidationErrors != null ?
Validation.GetErrors(group.Owner).Except(group.ValidationErrors).ToArray() : Validation.GetErrors(group.Owner).ToArray();
foreach (var error in leftOverErrors)
{
//HINT: BindingExpressionBase.Detach() removes the reference to BindingGroup, before ValidationErrors are removed.
if (error.BindingInError is BindingExpressionBase binding && (binding.Target == null ||
TreeHelper.IsDescendantOf(binding.Target, oldobj)) && binding.BindingGroup == null &&
(binding.ValidationErrors == null || binding.ValidationErrors.Count == 0 || !binding.ValidationErrors.Contains(error)))
{
typeof(Validation).GetMethod("RemoveValidationError", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] {error, group.Owner, group.NotifyOnValidationError});
}
}
}
}
private static BindingGroup FindBindingGroup(DependencyObject obj)
{
do
{
if (obj is FrameworkElement fe)
{
return fe.BindingGroup;
}
if (obj is FrameworkContentElement fce)
{
return fce.BindingGroup;
}
obj = LogicalTreeHelper.GetParent(obj);
} while (obj != null);
return null;
}
private static class TreeHelper
{
private static DependencyObject GetParent(DependencyObject element, bool recurseIntoPopup)
{
if (recurseIntoPopup)
{
// Case 126732 : To correctly detect parent of a popup we must do that exception case
Popup popup = element as Popup;
if ((popup != null) && (popup.PlacementTarget != null))
return popup.PlacementTarget;
}
Visual visual = element as Visual;
DependencyObject parent = (visual == null) ? null : VisualTreeHelper.GetParent(visual);
if (parent == null)
{
// No Visual parent. Check in the logical tree.
parent = LogicalTreeHelper.GetParent(element);
if (parent == null)
{
FrameworkElement fe = element as FrameworkElement;
if (fe != null)
{
parent = fe.TemplatedParent;
}
else
{
FrameworkContentElement fce = element as FrameworkContentElement;
if (fce != null)
{
parent = fce.TemplatedParent;
}
}
}
}
return parent;
}
public static bool IsDescendantOf(DependencyObject element, DependencyObject parent)
{
return TreeHelper.IsDescendantOf(element, parent, true);
}
public static bool IsDescendantOf(DependencyObject element, DependencyObject parent, bool recurseIntoPopup)
{
while (element != null)
{
if (element == parent)
return true;
element = TreeHelper.GetParent(element, recurseIntoPopup);
}
return false;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后将此属性绑定到 的Content属性DataGridCell。
<Window ...
xmlns:utils="clr-namespace:Utilities">
...
<DataGrid ...>
<DataGrid.CellStyle>
<Style BasedOn="{StaticResource {x:Type DataGridCell}}" TargetType="{x:Type DataGridCell}">
<Setter Property="utils:DataGridExtension.FixBindingGroupValidationErrorsFor" Value="{Binding Content, RelativeSource={RelativeSource Self}}" />
</Style>
</DataGrid.CellStyle>
</DataGrid>
...
</Window>
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
12004 次 |
| 最近记录: |