I have a control that at its most basic level is a ScrollViewer with a StackPanel (Orientation=Vertical) with a lot of TextBoxes in it.
<ScrollViewer>
<StackPanel x:Name="MyStackPanel"
Orientation="Vertical">
<TextBox Text="{Binding PropertyA, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding PropertyB, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding PropertyC, ValidatesOnDataErrors=True}" />
<!-- ... -->
<TextBox Text="{Binding PropertyX, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding PropertyY, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding PropertyZ, ValidatesOnDataErrors=True}" />
</StackPanel>
</ScrollViewer>
I want to scroll any controls with an error into view when the error occurs. So for example, if the user is at the top of the list and the TextBox bound to PropertyX is in error, then I want the ScrollViewer to scroll to it.
Currently I've inherited from ScrollViewer and added the following methods.
public void ScrollErrorTextBoxIntoView()
{
var controlInError = GetFirstChildControlWithError(this);
if (controlInError == null)
{
return;
}
controlInError.BringIntoView();
}
}
public Control GetFirstChildControlWithError(DependencyObject parent)
{
if (parent == null)
{
return null;
}
Control findChildInError = null;
var children = LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>();
foreach (var child in children)
{
var childType = child as Control;
if (childType == null)
{
findChildInError = GetFirstChildControlWithError(child);
if (findChildInError != null)
{
break;
}
}
else
{
var frameworkElement = child as FrameworkElement;
// If the child is in error
if (Validation.GetHasError(frameworkElement))
{
findChildInError = (Control)child;
break;
}
}
}
return findChildInError;
}
I'm having difficulty getting it to work properly. The way I see it, I have two options.
Attempt to get the ViewModel to execute the ScrollErrorTextBoxIntoView method. I'm not sure what the best way of doing that is. I was trying to set a property and acting from that but it didn't seem right (and it didn't work anyway)
Have the control do it in a self-contained way. This would require my ScrollViewer to listen to its children (recursively) and call the method if any of them are in an error state.
So my questions are:
Which one of those two options are better and how would you implement them?
Is there a better way of doing this? (Behaviors etc?) It MUST be MVVM.
NB. The GetFirstChildControlWithError was adapted from this question. How can I find WPF controls by name or type?