专注抽象

时间:2017-06-06 09:47:07

标签: c# wpf mvvm focus

带有按钮的

UserControl(其中一些被禁用)嵌套在其他UserControl内。窗口中同时显示了几个这样的内容。

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

我需要能够通过多个ViewModel传递焦点请求(通过属性?),最后在嵌套UserControl视图中触发它。

我可以抽象焦点请求吗?例如。我希望能够告诉&#34;将焦点设置到这个高级UserControl&#34;并且应该以某种方式自动通过嵌套UserControl及其按钮,因为只有按钮 可以获得焦点的元素。< / p>

的伪代码:

// 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)。我的问题是如何传递该请求(&#34;然后......,然后......,然后......&#34;在上面的例子中)。

有什么想法吗?

我正在寻找一种具有最高可重用性的简单直观的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)向传递焦点请求添加一些附加属性,这是不可聚焦的。

1 个答案:

答案 0 :(得分:2)

我认为您应该考虑将FrameworkElement.MoveFocus方法与FocusNavigationDirection.Next一起使用 - 这通常会给您预期的结果,即将焦点放在第一个遇到的可以接收键盘焦点的控件上。特别是这意味着将忽略不可聚焦的控件,禁用的控件和无法接收键盘焦点的控件(例如ItemsControlUserControl等)。这里唯一的问题是控件将以标签顺序遍历,但除非您正在弄乱它,否则它应该以深度优先的预订方式遍历可视树。所以这段代码:

UserControlA.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
如果UserControlA.UserControlB.ButtonDUserControlA的第一个支持键盘且启用后代的话,

应该关注FocusRequested

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

使用此类设置,您需要做的只是在ViewModelA中实施UserControlA,并将行为附加到FocusRequested。然后只需提升ViewModelA中的UserControlA.UserControlB.ButtonD即可将焦点移至var := '01/01/22'; update table1 t set t.date = to_date(var,'dd/mm/yy'); commit;