焦点抽象
Focus abstraction
UserControl
带有按钮(其中一些被禁用)嵌套在其他 UserControl
中。 window 中同时显示了多个这样的内容。
现在我需要将焦点设置到嵌套 UserControl
的第一个启用按钮,而选择焦点的逻辑将 运行 在 window 级别(例如,当 window 将启用某些 UserControl
).
我需要能够通过几个 ViewModel 传递焦点请求(通过属性?),并最终在嵌套的视图中触发它 UserControl
。
我可以摘要焦点请求吗?例如。我希望能够告诉 "set focus to this high level UserControl" 并且应该 自动 通过嵌套的 UserControl
及其按钮,因为只有按钮 是 什么元素 可以 获得焦点。
伪代码:
// in window
UserControlA.Focus();
// should in fact set focus to 4th button of nested user control
UserControlA.UserControlB.ButtonD.Focus();
// because of data templates it is actually more like this
var nested = UserControlA.ContentControl.Content as UserControlB;
var firstEnabledButton = nested.ItemsControl[3] as Button;
firstEnabledButton.SetFocus();
// and because of MVVM it may be as simple as
ViewModelA.IsFocused = true;
// but then A should run
ViewModelB.IsFocused = true;
// and then B should set property of button ViewModel
Buttons.First(o => o.IsEnabled).IsFocused = true.
// and then this has to be somehow used by the view (UserControlB) to set focus...
问题不在于如何在 MVVM 中设置焦点,这 can be done 不知何故(对于触发器,它需要丑陋的解决方法,其中 属性 首先设置为 false
)。我的问题是如何传递该请求(上面示例中的"and then ..., and then ..., and then...")。
有什么想法吗?
我正在寻找一种简单直观的 xaml 解决方案,具有最高的可重用性。我不想使用 ...IsFocused
属性和绑定向每个 ViewModel 和视图发送垃圾邮件。
我可以利用一些副作用来发挥我的优势,例如考虑这种行为
public static bool GetFocusWhenEnabled(DependencyObject obj) => (bool)obj.GetValue(FocusWhenEnabledProperty);
public static void SetFocusWhenEnabled(DependencyObject obj, bool value) => obj.SetValue(FocusWhenEnabledProperty, value);
public static readonly DependencyProperty FocusWhenEnabledProperty =
DependencyProperty.RegisterAttached("FocusWhenEnabled", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, (d, e) =>
{
var element = d as UIElement;
if (element == null)
throw new ArgumentException("Only used with UIElement");
if ((bool)e.NewValue)
element.IsEnabledChanged += FocusWhenEnabled_IsEnabledChanged;
else
element.IsEnabledChanged -= FocusWhenEnabled_IsEnabledChanged;
}));
static void FocusWhenEnabled_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)sender;
if (element.IsEnabled)
element.Dispatcher.InvokeAsync(() => element.Focus()); // invoke is a must
}
可用于自动启用焦点的元素。这需要一些 IsEnabled
逻辑 此外 并且在一些复杂的场景中很容易停止工作(启用不应导致聚焦)。
我在考虑是否可以添加一些附加的 属性 到 传递焦点请求 一直到 xaml(仅使用 xaml ) 尝试将焦点设置到不可聚焦的容器时。
我认为您应该考虑将 FrameworkElement.MoveFocus
方法与 FocusNavigationDirection.Next
一起使用 - 这通常会为您提供预期的结果,即将焦点放在遇到的第一个可以接收键盘焦点的控件上。特别是,这意味着不可聚焦的控件、禁用的控件和无法接收键盘焦点的控件(例如 ItemsControl
、UserControl
等)将被忽略。此处唯一的问题是控件将按 tab 顺序 遍历,但除非你搞砸它应该以深度优先预排序方式遍历可视化树。所以这段代码:
UserControlA.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
如果 UserControlA.UserControlB.ButtonD
是 UserControlA
的第一个可键盘聚焦且启用的后代,则它应该给予焦点。
在消除使用代码隐藏的必要性方面,我会做以下事情。首先,我会放弃使用视图模型属性来控制焦点。移动焦点在我看来更像是基于请求的概念而不是基于状态的概念,所以我会改用事件(例如 FocusRequested
)。为了使其可重用,我将创建一个单事件界面(例如 IRequestFocus
)。最后一点是创建一个行为,该行为将自动检查附加对象的 DataContext
是否实现 IRequestFocus
并在每次引发 FocusRequested
事件时调用 MoveFocus
。
有了这样的设置,您需要做的就是在 ViewModelA
中实现 IRequestFocus
,并将行为附加到 UserControlA
。然后简单地提高 ViewModelA
中的 FocusRequested
将导致将焦点移动到 UserControlA.UserControlB.ButtonD
.
UserControl
带有按钮(其中一些被禁用)嵌套在其他 UserControl
中。 window 中同时显示了多个这样的内容。
现在我需要将焦点设置到嵌套 UserControl
的第一个启用按钮,而选择焦点的逻辑将 运行 在 window 级别(例如,当 window 将启用某些 UserControl
).
我需要能够通过几个 ViewModel 传递焦点请求(通过属性?),并最终在嵌套的视图中触发它 UserControl
。
我可以摘要焦点请求吗?例如。我希望能够告诉 "set focus to this high level UserControl" 并且应该 自动 通过嵌套的 UserControl
及其按钮,因为只有按钮 是 什么元素 可以 获得焦点。
伪代码:
// in window
UserControlA.Focus();
// should in fact set focus to 4th button of nested user control
UserControlA.UserControlB.ButtonD.Focus();
// because of data templates it is actually more like this
var nested = UserControlA.ContentControl.Content as UserControlB;
var firstEnabledButton = nested.ItemsControl[3] as Button;
firstEnabledButton.SetFocus();
// and because of MVVM it may be as simple as
ViewModelA.IsFocused = true;
// but then A should run
ViewModelB.IsFocused = true;
// and then B should set property of button ViewModel
Buttons.First(o => o.IsEnabled).IsFocused = true.
// and then this has to be somehow used by the view (UserControlB) to set focus...
问题不在于如何在 MVVM 中设置焦点,这 can be done 不知何故(对于触发器,它需要丑陋的解决方法,其中 属性 首先设置为 false
)。我的问题是如何传递该请求(上面示例中的"and then ..., and then ..., and then...")。
有什么想法吗?
我正在寻找一种简单直观的 xaml 解决方案,具有最高的可重用性。我不想使用 ...IsFocused
属性和绑定向每个 ViewModel 和视图发送垃圾邮件。
我可以利用一些副作用来发挥我的优势,例如考虑这种行为
public static bool GetFocusWhenEnabled(DependencyObject obj) => (bool)obj.GetValue(FocusWhenEnabledProperty);
public static void SetFocusWhenEnabled(DependencyObject obj, bool value) => obj.SetValue(FocusWhenEnabledProperty, value);
public static readonly DependencyProperty FocusWhenEnabledProperty =
DependencyProperty.RegisterAttached("FocusWhenEnabled", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, (d, e) =>
{
var element = d as UIElement;
if (element == null)
throw new ArgumentException("Only used with UIElement");
if ((bool)e.NewValue)
element.IsEnabledChanged += FocusWhenEnabled_IsEnabledChanged;
else
element.IsEnabledChanged -= FocusWhenEnabled_IsEnabledChanged;
}));
static void FocusWhenEnabled_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)sender;
if (element.IsEnabled)
element.Dispatcher.InvokeAsync(() => element.Focus()); // invoke is a must
}
可用于自动启用焦点的元素。这需要一些 IsEnabled
逻辑 此外 并且在一些复杂的场景中很容易停止工作(启用不应导致聚焦)。
我在考虑是否可以添加一些附加的 属性 到 传递焦点请求 一直到 xaml(仅使用 xaml ) 尝试将焦点设置到不可聚焦的容器时。
我认为您应该考虑将 FrameworkElement.MoveFocus
方法与 FocusNavigationDirection.Next
一起使用 - 这通常会为您提供预期的结果,即将焦点放在遇到的第一个可以接收键盘焦点的控件上。特别是,这意味着不可聚焦的控件、禁用的控件和无法接收键盘焦点的控件(例如 ItemsControl
、UserControl
等)将被忽略。此处唯一的问题是控件将按 tab 顺序 遍历,但除非你搞砸它应该以深度优先预排序方式遍历可视化树。所以这段代码:
UserControlA.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
如果 UserControlA.UserControlB.ButtonD
是 UserControlA
的第一个可键盘聚焦且启用的后代,则它应该给予焦点。
在消除使用代码隐藏的必要性方面,我会做以下事情。首先,我会放弃使用视图模型属性来控制焦点。移动焦点在我看来更像是基于请求的概念而不是基于状态的概念,所以我会改用事件(例如 FocusRequested
)。为了使其可重用,我将创建一个单事件界面(例如 IRequestFocus
)。最后一点是创建一个行为,该行为将自动检查附加对象的 DataContext
是否实现 IRequestFocus
并在每次引发 FocusRequested
事件时调用 MoveFocus
。
有了这样的设置,您需要做的就是在 ViewModelA
中实现 IRequestFocus
,并将行为附加到 UserControlA
。然后简单地提高 ViewModelA
中的 FocusRequested
将导致将焦点移动到 UserControlA.UserControlB.ButtonD
.