c# - 焦点抽象

标签 c# wpf mvvm focus

UserControl带有按钮(其中一些被禁用)嵌套在其他 UserControl 中.有几个这样的窗口一次显示。

现在我需要将焦点设置为嵌套 UserControl 的第一个启用按钮,而选择焦点的逻辑将在窗口级别上运行(例如,当窗口将启用某些 UserControl 时)。

我需要能够通过几个 ViewModel 传递该焦点请求(通过属性?),最后在嵌套 UserControl 的 View 中触发它.

何可以我摘要焦点请求 ?例如。我希望能够告诉“将焦点设置到这个高级用户控件”并且应该以某种方式自动通过嵌套 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 )。我的问题是如何传递该请求(“然后......,然后......,然后......”在上面的例子中)。

有任何想法吗?

我正在寻找一个简单直观且可重用性最高的 xaml 解决方案。我不想使用 ...IsFocused 向每个 ViewModel 和 View 发送垃圾邮件属性和绑定(bind)。

我可以利用一些副作用来发挥我的优势,例如考虑这种行为
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 - 这通常会给你预期的结果,即把焦点放在第一个遇到的可以接收键盘焦点的控件上。特别是,这意味着将省略不可聚焦的控件、禁用的控件和无法接收键盘焦点的控件(例如 ItemsControlUserControl 等)。这里唯一的问题是控件将在 中遍历。标签顺序 ,但除非你搞砸了,它应该以深度优先的预购方式遍历视觉树。所以这段代码:

UserControlA.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));

应该关注 UserControlA.UserControlB.ButtonD如果它是 UserControlA 的第一个可键盘聚焦并启用的后代.

在消除使用代码隐藏的必要性方面,我要做的是以下内容。首先,我会放弃使用 View 模型属性来控制焦点。在我看来,移动焦点更像是基于请求的概念而不是基于状态的概念,所以我会使用事件(例如 FocusRequested )来代替。为了使其可重用,我将创建一个单事件接口(interface)(例如 IRequestFocus )。最后一步是创建一个自动检查 DataContext 的行为。的附加对象实现 IRequestFocus并调用MoveFocus每次FocusRequested引发事件。

有了这样的设置,你需要做的就是实现 IRequestFocusViewModelA ,并将行为附加到 UserControlA .然后简单地提高FocusRequestedViewModelA将导致将焦点移至 UserControlA.UserControlB.ButtonD .

关于c# - 焦点抽象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44386672/

相关文章:

c# - 使用 IConverter 处理 WPF/XAML/MVVM 中的 {NewItemPlaceholder}

c# - 如何递归返回直接对象中 'toy' 项目的数量以及该对象的所有直接子对象

c# - ASP.NET MVC : how to split a row in a text file by fixed-width and pass to a model

c# - 在sql查询中自定义排序

wpf - WPF DataGrid 如何让卡住的行/列工作?

c# - 在 ViewModel 中存储信息或使用 IValueConverter

c# - 在 Dynamics 2011 中检索选项集

c# - 使用 XmlSerializer 读取 XML 文件后,我的所有 IsDirty 标志都设置为 true

C# WPF DataGrid 删除每个选定的项目

c# - MVVM:如何对控件进行函数调用?