Caliburn Micro Recipe: Controlling Focus from the ViewModel

 The Common Problem

 Setting the focus in the UI is tricky when using MVVM because the ViewModel doesn’t “know” about the View, and certainly doesn’t have the ability to make calls like “myView.myControl.Focus().”

In the WPF application I am building with Caliburn Micro I had the need to control the focus from the ViewModel in a number of places.  For instance, on my login page, if the user entered incorrect credentials and click the “Login” button, I needed the ability to set the focus back to the TextBox for the user name. 
Additionally when the login page first gets displayed, I needed the focus to be initially set to the user name TextBox to minimize unnecessary mouse clicks and tabbing for the user. 

All of the above issues are demonstrated in the attached sample solution.  (Please note that the sample solution is going against a one-off version of the Caliburn Micro binaries–I describe the minor changes that I made to the Caliburn source below).

 How to Set the Focus From the ViewModel

 So, how can the ViewModel set the focus in the View?  Very simply.  If there is a LoginView with this XAML:

             <TextBox x:Name=Username/>

…and a LoginViewModel with this property:

            public string Username { get; set;}

…then simply create a new property of type bool that follows the naming convention IsMyPropertyNameFocused.  So for the current example you would add

             public bool IsUsernameFocused { get; set;}

If the same LoginViewModel then has a LogIn() method that gets invoked when the user clicks a button named Login, it then has the ability to control when the UIElement bound to the Username property receives focus like so:

 public void LogIn()
{
            //just assume login fails for simplicity
            DisplaySomeErrorMessageToUser();
            IsUsernameFocused = true;
            NotifyOfPropertyChange(() => IsUsernameFocused);
}

 I have tested this approach with WPF and it should also work with Silverlight since this “MVVM focus by convention” approach works via attached behaviors.  I have no idea whether this will work with WP7 or not.

 Changes to Caliburn Micro Source Code

I can’t take the credit for inventing the ideas I use to accomplish this.  All I’ve done is take the clever code of others that I’ve found online and incorporate them into Caliburn Micro’s “Convention Over Configuration” approach to make this sort of thing easier to do.  The central idea of using a “FocusExtension” attached behavior to set a control’s focus from a ViewModel I got from StackOverflow user “Anvaka” here.

I did make some minor code changes to Anvaka’s approach, namely setting the focus on the Dispatcher with a Background priority so that it will still get set even if the control isn’t loaded when the ViewModel tries to set it and also flipping the focus attached property back to false immediately after it gets set.

Here are the changes I made:

1. Add the FocusExtension class in the Caliburn Micro Silverlight project (with a few preprocessor directives so it will work for WPF also) and then in the Caliburn Micro WPF project pick Add Existing Item and select FocusExtension and click the dropdown arrow on the Add button and choose “Add as Link”.

 //this great implementation taken from Anvaka’s answer on StackOverflow here:
 //http://stackoverflow.com/questions/1356045/set-focus-on-textbox-in-wpf-from-view-model-c-wpf/1356781#1356781
    public static class FocusExtension
    {
        public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsFocusedProperty);
        }

         public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }

#if SILVERLIGHT
        public static readonly DependencyProperty IsFocusedProperty =
                DependencyProperty.RegisterAttached(
                 “IsFocused”, typeof(bool), typeof(FocusExtension),
                new PropertyMetadata(false, OnIsFocusedPropertyChanged));
#else
        public static readonly DependencyProperty IsFocusedProperty =
         DependencyProperty.RegisterAttached(
          “IsFocused”, typeof(bool),  typeof(FocusExtension),
          new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
#endif
        private static void OnIsFocusedPropertyChanged(DependencyObject d,
                DependencyPropertyChangedEventArgs e)
        {
#if SILVERLIGHT
            var uie = (Control)d;
#else
            var uie = (UIElement)d;
#endif
            if ((bool)e.NewValue)
            {
                uie.Dispatcher.BeginInvoke(new System.Action(delegate
                {
                    uie.Focus(); // Don’t care about false values.
#if !SILVERLIGHT
                    Keyboard.Focus(uie);
#endif
                    //need to switch it back to false right away so we can detect future change notifications
                    //back to true
                    SetIsFocused(d, false);
#if !SILVERLIGHT
                }), DispatcherPriority.Background, null);
#else
                }), null); //this SHOULD work in Silverlight because I understand that the only DispatcherPriority is “Background”
#endif
            }
        }
    }

2.  Add the CheckForFocusProperty method in ViewModelBinder

private static void CheckForFocusProperty(string focusPropertyName, PropertyInfo[] properties, FrameworkElement foundControl)
        {
            if (properties.FirstOrDefault(prop => prop.Name.Equals(focusPropertyName, StringComparison.InvariantCultureIgnoreCase)) != null)
            {
                ViewModelBinder.SetBindingOnFocusExtensionAttachedBehavior(focusPropertyName, foundControl);
            }
        }

3. Add the new SetBindingOnFocusExtensionAttachedBehavior method in the ViewModelBinder class in the Caliburn Micro Silverlight project

private static void SetBindingOnFocusExtensionAttachedBehavior(string bindingPath, FrameworkElement foundControl)
{
            Binding b = new Binding(bindingPath);
            b.Mode = BindingMode.TwoWay;
            BindingOperations.SetBinding(foundControl, FocusExtension.IsFocusedProperty, b);
}

4. Call the CheckForFocusProperty method from the BindProperties and BindActions methods in ViewModelBinder.

 That’s it.  Now if you build Caliburn Micro and reference the appropriate Caliburn Micro binaries in your WPF or Silverlight project you can start adding boolean properties with the IsMyPropertyNameFocused convention and then set them to true and raise a property change notification to set focus in the view.

Other Useful Things Shown in the Sample Solution

Have a look at the attached sample solution to see this kind of functionality in action.In addition to setting focus from the view model there are a couple of other helpful things for MVVM developers that are illustrated:

1. using the WPF PasswordBox with MVVM via PasswordBoxHelper attached behavior (not my idea, I got this from Samuel Jack here)–note that is only necessary for the WPF PasswordBox, it is not a problem in SL

2. selecting all the Text in a TextBox when it receives focus via attached behavior (not my idea, I got this from Sergey Aldoukhov here)

The Code

Here’s the source for both the same project and the modified version of Caliburn.Micro. (I suck at blogging so right click the links and Save Target As…then rename them from .doc to .zip).
Caliburn.Micro.FocusExample
Caliburn.Micro.FocusChanges

Enjoy,

Josh

Advertisement

1 comment so far

  1. Tiago Freitas Leal on

    Very interesting post. Unfortunately Caliburn.Micro changed a lot. Do you care to update the changes file?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.