上面的做法实现了我的需求,而且使用这种方案可以让 ViewModel 对 View 有更多的控制权,可以指定哪个 UI 元素在任何时间获得焦点,但坏处就是要写很多代码,而且属性越多耦合越多。
另一种做法是让 Validation.HasError 为 true 的控件自动获得焦点,可以在 View 上添加这个样式:
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type Control}}"> <Style.Triggers> <DataTrigger Binding="{Binding (Validation.HasError),RelativeSource={RelativeSource Mode=Self}}" Value="True"> <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Mode=Self}}"/> </DataTrigger> </Style.Triggers> </Style>ViewModel 中可以不负责处理焦点,只负责验证数据:
private void Submit() { ErrorsContainer.ClearErrors(); if (string.IsNullOrEmpty(Name)) ErrorsContainer.SetErrors(nameof(Name), new List<string> { "请输入名称" }); }这个全局 Style 让所有 TextBox 都添加一个绑定到 Validation.HasError 的 DataTrigger,当 Validation.HasError 为 True 时 TextBox 获得焦点。这种做法可以写少很多代码,但对具体业务来说可能不是很好用。
6. 最后这篇文章只介绍了简单的解决方案,最后还是需要根据自己的业务需求进行修改或封装。View 和 ViewModel 交互可以是一个很庞大的话题,下次有机会再深入探讨。
7. 参考FocusManager.FocusedElement 附加属性