【问题标题】:WPF-MVVM: Setting UI control focus from ViewModelWPF-MVVM:从 ViewModel 设置 UI 控件焦点
【发布时间】:2011-07-17 11:12:57
【问题描述】:

在 MVVM 架构中设置控制焦点的良好做法是什么。

我设想的方式是使用 ViewModel 上的一个属性,该属性会在需要时触发焦点更改。而不是让 UI 控件绑定/侦听该属性,以便如果它发生更改,将设置适当的焦点。

我将其视为 ViewModel 的事情,因为我想在 ViewModel 执行某个操作(例如加载某些数据)之后适当地设置焦点。

最佳做法是什么?

【问题讨论】:

标签: c# .net wpf mvvm focus


【解决方案1】:

如果您使用的是 Caliburn.Micro,这是我创建的一项服务,用于将焦点设置为从 Screen 继承的视图中的任何控件。

注意:这仅在您将 Caliburn.Micro 用于您的 MVVM 框架时才有效。

public static class FocusManager
{
    public static bool SetFocus(this IViewAware screen ,Expression<Func<object>> propertyExpression)
    {
        return SetFocus(screen ,propertyExpression.GetMemberInfo().Name);
    }

    public static bool SetFocus(this IViewAware screen ,string property)
    {
        Contract.Requires(property != null ,"Property cannot be null.");
        var view = screen.GetView() as UserControl;
        if ( view != null )
        {
            var control = FindChild(view ,property);
            bool focus = control != null && control.Focus();
            return focus;
        }
        return false;
    }

    private static FrameworkElement FindChild(UIElement parent ,string childName)
    {
        // Confirm parent and childName are valid. 
        if ( parent == null || string.IsNullOrWhiteSpace(childName) ) return null;

        FrameworkElement foundChild = null;

        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);

        for ( int i = 0; i < childrenCount; i++ )
        {
            FrameworkElement child = VisualTreeHelper.GetChild(parent ,i) as FrameworkElement;
            if ( child != null )
            {

                BindingExpression bindingExpression = GetBindingExpression(child);
                if ( child.Name == childName )
                {
                    foundChild = child;
                    break;
                }
                if ( bindingExpression != null )
                {
                    if ( bindingExpression.ResolvedSourcePropertyName == childName )
                    {
                        foundChild = child;
                        break;
                    }
                }
                foundChild = FindChild(child ,childName);
                if ( foundChild != null )
                {
                    if ( foundChild.Name == childName )
                        break;
                    BindingExpression foundChildBindingExpression = GetBindingExpression(foundChild);
                    if ( foundChildBindingExpression != null &&
                        foundChildBindingExpression.ResolvedSourcePropertyName == childName )
                        break;
                }

            }
        }

        return foundChild;
    }

    private static BindingExpression GetBindingExpression(FrameworkElement control)
    {
        if ( control == null ) return null;

        BindingExpression bindingExpression = null;
        var convention = ConventionManager.GetElementConvention(control.GetType());
        if ( convention != null )
        {
            var bindablePro = convention.GetBindableProperty(control);
            if ( bindablePro != null )
            {
                bindingExpression = control.GetBindingExpression(bindablePro);
            }
        }
        return bindingExpression;
    }
}

这个怎么用?

从继承自 Caliburn.Micro.Screen 或 Caliburn.Micro.ViewAware 的 ViewModel

this.SetFocus(()=&gt;ViewModelProperty); 要么 this.SetFocus("Property");

它是如何工作的?

此方法将尝试在可视化视图树中搜索元素,并将焦点设置到任何匹配的控件。如果没有找到这样的控件,那么它将使用 Caliburn.Micro 使用的 BindingConventions。

例如,

它将在控件的BindingExpression 中查找Propery。 对于 TextBox,它会查看该属性是否绑定到 Text 属性,然后设置焦点。

【讨论】:

  • 这很好,我在很多项目中都使用过它。感谢此代码:)
  • 很好的解决方案!
【解决方案2】:

按照此处答案中的建议使用 IsFocused 附加属性:Set focus on textbox in WPF from view model (C#)

然后您可以简单地绑定到视图模型中的属性。

【讨论】:

  • 谢谢,但如果我正确阅读了这个例子,我会从视图模型中定位特定控件并在代码中调用 set focus?我认为这打破了 MVVM 哲学。理想情况下,我希望视图模型公开一个属性/事件,并且应该由 View 来做一些事情,例如将焦点设置在它的一个控件上。我想避免命名 UI 控件。
  • 那根本不是那里发生的事情。 UI 元素有一个附加属性,该属性绑定到 ViewModel 中的 bool 属性。附加属性的工作是将焦点设置到具有附加属性的控件。那就是MVVM。如果不清楚,我建议您使用 Anvaka 提出的答案。
  • @Sonic Soul:我明白你的意思,但视图模型仍然没有直接的控制知识,所以我不认为有任何违反 MVVM 原则的说法。
  • 啊,我明白了,是的,Anvakas 的答案更符合我的要求,谢谢
  • 使用附加属性将事件和属性更改数据从视图传送到 VM 是一项很棒的技术。起初我持怀疑态度,但在看到它在 Caliburn Micro 中很好地使用并反思 WPF 中的附加属性系统以像这样扩展 UI 绑定系统的目的后,我接受了它对我的优势。现在,我可以轻松地将视图和 VM 分开,但在存在间隙时可以灵活地使用 WPF 绑定来桥接它们。
【解决方案3】:

ViewModel 向 View 抛出一个事件,告诉它动作已经完成,View 将设置焦点。

【讨论】:

  • 一个事件很难管理,因为你必须使用后面的代码。附加的属性给你同样的效果,你可以使用绑定——更容易编码和维护。
猜你喜欢
  • 2023-01-07
  • 2017-06-29
  • 1970-01-01
  • 2020-10-14
  • 2011-06-09
  • 1970-01-01
  • 2013-02-28
  • 1970-01-01
  • 2013-01-02
相关资源
最近更新 更多