WPF ItemsControl ItemTemplate Validation
Is there a way to determine if an ItemsControl has any child contr开发者_StackOverflow中文版ols with validation errors? I would like to bind this value (boolean) to the IsEnabled property on a Button.
I've recently used the code from this previous SO answer Detecting WPF Validation Errors to good effect for exactly this.
I have a user control which contains a DataGrid. The usercontrol exposes a IsValid
property which contains a getter, that simply calls the static IsValid function passing in the DataGrid as the DependencyObject:
public class MyControl : UserControl
{
public bool IsValid
{
get { return Validator.IsValid(MyDataGrid); }
}
}
The control's IsValid
property can then be checked by the CanExecute function of the command you bind to the button you want to enable/disable.
My only issue with the code that i linked to was that it actually evaluates the validations on the bindings, this means as soon as you run it any field that is technically invalid but hasn't yet been invalidated (i.e. the user may not have entered any data in that field yet because they haven't got to it) will now be in an invalid state - i haven't yet looked at a way to avoid or mitigate this.
Edit:
here is an updated version that doesn't invalidate the controls as i mentioned previously. I've simply commented out/slightly changed some lines but left everything in there so you can see the difference. Note that this should also perform faster as you will be exiting the moment you find the first invalid binding.
public static bool IsValid(DependencyObject parent)
{
// Validate all the bindings on the parent
bool valid = true;
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext())
{
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property))
{
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
foreach (ValidationRule rule in binding.ValidationRules)
{
ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
if (!result.IsValid)
{
//BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
//System.Windows.Controls.Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
//valid = false;
return false;
}
}
}
}
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (!IsValid(child))
{
//valid = false;
return false;
}
}
//return valid;
return true;
}
I don't know why, slugter's answer didn't work for me (LocalValueEnumerator
returned some properties but never the binded ones, like Text
).
I managed to get it working with this code (derived from this answer):
public static bool IsValid(DependencyObject obj)
{
// The dependency object is valid if it has no errors,
//and all of its children (that are dependency objects) are error-free.
return !Validation.GetHasError(obj) &&
GetVisualTreeChildren(obj)
.OfType<DependencyObject>()
.All(child => IsValid(child));
}
//VisualTreeHelper don't have a method to get all the children of a visual object
private static IEnumerable GetVisualTreeChildren(DependencyObject parent)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
yield return VisualTreeHelper.GetChild(parent, i);
}
精彩评论