52

What is a good practice of setting control focus in MVVM architecture.

The way I envision it, is with a property on the ViewModel that would trigger a focus change when needed. And than have the UI controls bind/listen to that property so that if it changes, appropriate focus will be set.

I see it as a ViewModel thing, because i want to set focus appropriate after a certain action was performed by the ViewModel, such as loading certain data.

What's the best practice?

Sonic Soul
  • 23,855
  • 37
  • 130
  • 196
  • this solution is the most elegant for the task http://stackoverflow.com/a/6742741/1447518 – Idan Apr 09 '13 at 12:34
  • Does this answer your question? [Set focus on TextBox in WPF from view model](https://stackoverflow.com/questions/1356045/set-focus-on-textbox-in-wpf-from-view-model) – Ian Kemp Jan 05 '21 at 15:32

3 Answers3

33

Use the IsFocused Attached Property as suggested in the Answer here: Set focus on textbox in WPF from view model (C#)

Then you can simply bind to a property in your viewmodel.

Community
  • 1
  • 1
Snarfblatt
  • 522
  • 4
  • 9
  • thanks, but if i am reading this example correctly, i would be targeting specific controls from view model and calling set focus in code?. i think that breaks the MVVM philosophy. ideally i'd want the view model to expose a property/event, and it should be up to View to do something about it, such as setting focus on one of its controls. i'd like to avoid having to name UI controls. – Sonic Soul Mar 17 '11 at 16:14
  • 7
    That's not at all what is going on there. The UI element has an attached property that is bound to a bool property in the ViewModel. The job of the attached property is to set focus to the control that has the attached property. That is MVVM. If it wasn't clear I was suggesting you use the answer proposed by Anvaka. – Snarfblatt Mar 17 '11 at 16:19
  • 1
    @Sonic Soul: I see your point, but the viewmodel still would have no direct knowledge of the control, so I don't think there is any violation of MVVM principles per say. – Liz Mar 17 '11 at 16:27
  • ah, i see, yes Anvakas answer is more inline to what i was looking for, thanks – Sonic Soul Mar 17 '11 at 16:34
  • @Sonic Soul: sorry I wasn't more clear in my initial answer. :) – Snarfblatt Mar 17 '11 at 16:35
  • 1
    Using an Attached Property to carry event and property change data to the VM from the View is a great technique. I was skeptical at first, but after seeing it used well in Caliburn Micro and reflecting on the purpose of an attached property system in WPF to extend the UI binding system like this, I've embraced it to my advantage. Now I can keep the views and VMs separate with ease, yet have the flexibility to bridge them using WPF binding when where there are gaps. – codekaizen Mar 17 '11 at 17:18
  • This is a surprisingly elegant solution. I don't even need a XxxHasFocus property in the VM, I just bind it to the XxxIsOpen property that displayed the popup that holds the control. – Jonathan Allen Sep 30 '13 at 23:30
17

If you are using Caliburn.Micro, here is a service that I created to set Focus to any Control in the view inherited from Screen.

Note: This will only work if you are using Caliburn.Micro for your MVVM framework.

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;
    }
}

How to use this?

From your ViewModel inherited from Caliburn.Micro.Screen or Caliburn.Micro.ViewAware

this.SetFocus(()=>ViewModelProperty); or this.SetFocus("Property");

How it works?

This method will try to search for an element in the Visual Tree of View and focus will be set to any matching control. If no such control found, then it will make use of the BindingConventions used by Caliburn.Micro.

For ex.,

It will look for the Propery in the BindingExpression of the control. For TextBox, it will look whether this property is binded to Text property then the focus will be set.

Kishore Kumar
  • 12,675
  • 27
  • 97
  • 154
1

The ViewModel throws an event to the View telling it that the action has been completed, and the View will set the focus.

Alex Shtoff
  • 2,520
  • 1
  • 25
  • 53
  • 2
    An event is hard to manage, since you have to use code behind. An attached property gives you the same effect, and you can use binding - much easier to code and maintain. – codekaizen Mar 17 '11 at 17:19