我有一个控件,在最基本的级别是一个带有StackPanel(Orientation = Vertical)的ScrollViewer,里面有很多TextBox。
<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>
我想在发生错误时将带有错误的任何控件滚动到视图中。因此,例如,如果用户位于列表顶部且绑定到PropertyX的TextBox出错,那么我希望ScrollViewer滚动到它。
目前我从ScrollViewer继承并添加了以下方法。
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;
}
我很难让它正常工作。我看到它的方式,我有两个选择。
尝试让ViewModel执行ScrollErrorTextBoxIntoView方法。我不确定这样做的最佳方法是什么。我试图设置一个属性并从那起行动,但它看起来并不正确(并且它无论如何也没有用)
让控件以独立的方式执行。这将要求我的ScrollViewer监听其子节点(递归地),并在其中任何一个处于错误状态时调用该方法。
所以我的问题是:
这两个选项中哪一个更好,你会如何实现它们?
有更好的方法吗? (行为等?)必须是MVVM。
NB。 GetFirstChildControlWithError是根据这个问题改编的。 How can I find WPF controls by name or type?
答案 0 :(得分:1)
在以下假设下工作:
INotifyPropertyChanged
和IDataErrorInfo
IDataErrorInfo.Error
属性不为空。基本上,您希望监听DataContext属性更改并查明是否存在DataError。
如果您查看behaviors,则可以在不继承ScrollViewer
的情况下解决此问题。
以下是一个示例实现:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class ScrollToFirstInvalidElementBehavior : Behavior<ScrollViewer>
{
protected override void OnAttached()
{
ResetEventHandlers(null, AssociatedObject.DataContext);
AssociatedObject.DataContextChanged += OnDataContextChanged;
}
protected override void OnDetaching()
{
AssociatedObject.DataContextChanged -= OnDataContextChanged;
}
private void OnDataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
ResetEventHandlers(e.OldValue, e.NewValue);
}
private void ResetEventHandlers(object oldValue, object newValue)
{
var oldContext = oldValue as INotifyPropertyChanged;
if (oldContext != null)
{
oldContext.PropertyChanged -= OnDataContextPropertyChanged;
}
var newContext = newValue as INotifyPropertyChanged;
if (newContext is IDataErrorInfo)
{
newContext.PropertyChanged += OnDataContextPropertyChanged;
}
}
private void OnDataContextPropertyChanged(object sender,
PropertyChangedEventArgs e)
{
var dataError = (IDataErrorInfo) sender;
if (!string.IsNullOrEmpty(dataError.Error))
{
var controlInError = GetFirstChildControlWithError(AssociatedObject);
if (controlInError != null)
{
controlInError.BringIntoView();
}
}
}
private Control GetFirstChildControlWithError(ScrollViewer AssociatedObject)
{
//...
}
}