这是我想出的解决方案,它允许具有依赖属性的将其绑定到的视图模型的验证“包装”起来。
首先,我按照
此帖子中的模式创建了所需的层次结构。
XAML:
<!-- Some boilerplate attributes snipped -->
<UserControl x:Class="App.Views.UserControls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:App.Views.UserControls"
Validation.ErrorTemplate="{x:Null}">
<Grid x:Name="LayoutRoot"
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MyUserControl}}">
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
这样,控件的DataContext就是从父级继承的视图模型,这也是验证完成的地方。然后在控件的根子元素上覆盖它自己,这允许在代码后台绑定依赖属性。还要注意,控件的ErrorTemplate
已被清空 - 这是为了防止默认的红框出现。
继承的视图模型现在可以很简单地从控件的代码后台中访问:
private INotifyDataErrorInfo ViewModelErrors => DataContext as INotifyDataErrorInfo
现在在用户控件中实现INotifyDataErrorInfo
并包装视图模型:
public bool HasErrors => ViewModelErrors.HasErrors;
public IEnumerable GetErrors(string propertyName)
{
return ViewModelErrors.GetErrors(propertyName);
}
当你需要知道哪个模型属性绑定到你的控制依赖属性时,就会出现棘手的问题。如果可以通过名称查找注册的依赖属性并查询绑定,那么这将更容易,但我没有找到一种不使用反射进行此操作的方法。因此,我使用依赖属性的PropertyChangedCallback
手动构建了映射列表。回调的参数包含所有所需信息。
private readonly Dictionary<string, string> _propertyMappings = new Dictionary<string, string>();
private static void OnDependencyPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var userControl = (MyUserControl)d;
var dependencyPropertyName = e.Property.Name;
if (!userControl._propertyMappings.ContainsKey(dependencyPropertyName))
{
var binding = BindingOperations.GetBindingExpression(d, e.Property);
if (binding != null)
{
var boundPropertyName = binding.ResolvedSourcePropertyName;
userControl._propertyMappings[dependencyPropertyName] = boundPropertyName;
}
}
}
然后将其合并到GetErrors
中:
public IEnumerable GetErrors(string propertyName)
{
if (ViewModelErrors != null && _propertyMappings.ContainsKey(propertyName))
{
return ViewModelErrors.GetErrors(_propertyMappings[propertyName]);
}
else
{
return Enumerable.Empty<string>();
}
}
应该足够了。验证在模型中完成,结果被拉到用户控件中。不需要重复。