Handling Focus with Caliburn.Micro

Topics: Conventions, Framework Services, Getting Started, UI Architecture
Jan 5, 2014 at 4:36 PM
Edited Jan 5, 2014 at 4:40 PM
I have been searching for a solution for setting Focus from ViewModel. I couldn't find one as I was expecting, so I wrote a one for myself. I am posting here, so that I can help someone.

The following class will Set Focus to a named control in View, or if there is no named control then look for a control where the binding is defined with the property from ViewModel. For the later part, it consumes the binding conventions defined in Caliburn.Micro.ConventionManager.

This solution will work only for ViewModel that are derived from Caliburn.Micro.Screen or Caliburn.Micro.ViewAware.

I thought of using this as a service so I defined the class as static.
public static class FocusManager
{
    /// <summary>
    /// Set Keyboard Focus to the element named after the property.
    /// </summary>
    /// <param name="screen">View in which the control exists.</param>
    /// <param name="propertyExpression">Expression for the property <example>()=>Property</example></param>
    /// <returns>true, if focused is set.</returns>
    public static bool SetFocus(this IViewAware screen ,Expression<Func<object>> propertyExpression)
    {
        return SetFocus(screen ,propertyExpression.GetMemberInfo().Name);
    }

    /// <summary>
    /// Set Keyboard Focus to the element named after the property.
    /// </summary>
    /// <param name="screen">View in which the control exists.</param>
    /// <param name="property">Name of the property</param>
    /// <returns>true, if focused is set.</returns>
    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;
    }

    /// <summary>
    /// Find the control in the Visual Tree and also looking at the Binding Expression.
    /// </summary>
    /// <param name="parent">View in which the control exists.</param>
    /// <param name="childName">Named of the property which is defined the ViewModel.</param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// Get the BindingExpression for the control using <see cref="Caliburn.Micro.ConventionManager"/>
    /// </summary>
    /// <param name="control">Control for which,BindingExpression is required.</param>
    /// <returns>BindingExpression for the control.</returns>
    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;
    }
}
Jun 27, 2014 at 12:28 PM
Edited Jun 27, 2014 at 12:30 PM
Thanks @kishorejangid, it works !!! you save my time ... :)
I add few lines to enable/disable a control


public static void Enable(this IViewAware screen, Expression<Func<object>> propertyExpression)
    { Enable(screen, propertyExpression.GetMemberInfo().Name); }

    public static void Enable(this IViewAware screen, string property)
    {
        Contract.Requires(property != null, "Property cannot be null.");
        var view = screen.GetView() as UserControl;
        if (view == null) return;
        var control = FindChild(view, property);
        if (control != null) { control.IsEnabled = true; }
    }

    public static void Disable(this IViewAware screen, Expression<Func<object>> propertyExpression)
    {  Disable(screen, propertyExpression.GetMemberInfo().Name); }

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