Handling Focus?

Aug 9, 2010 at 8:44 AM

Hi 

Is there a 'best practice' for dealing with focus? I have a  usercontrol wrapped inside a popup control. When the popup control opens, I am wanting to set the focus to the one of the child controls of the wrapped usercontrol. I've got the messaging working so that the usercontrol's viewmodel receives an event that the popup has opened, but am unsure how to communicate this onto the view.   I'm aware of the FocusManager in WPF but it doesn't quite give me the functionality I'm after. 

I was wondering about injecting an eventaggregator into my views, but am unsure of the mechanism by which I could do that with Caliburn.Micro or in fact if it is the best approach (e.g. is that even suitable for an SL version). What I like about it is that it obviously keeps the decoupling between view and view model.  FYI, my project is viewmodel-driven atm. But when I step the code, I can't quite work out  the extension point when the view is constructed where I could  inject the eventaggregator.

Thx again for any advice.

Simon

 

Coordinator
Aug 9, 2010 at 1:35 PM
Can you clarify why the FocusManager is unsuitable for your scenario?
Aug 9, 2010 at 2:21 PM

Caveat : Perhaps my design isn't quite right ...

I have a main window with a textbox and a usercontrol nested in a popup control. I need to set focus to a listbox in the nested usercontrol on a keystroke in the textbox. I get the nested usercontrol to appear, but can't work out how to set the focus to the listbox in the child usercontrol.  I tried putting

FocusManager.FocusedElement="{Binding ElementName=mylistbox}"

in the child Usercontrol's UserControl tag, but that had no effect. Focus remains on the textbox. I found an article (http://www.julmar.com/blog/mark/PermaLink,guid,03722678-882a-4bb4-928f-4fe0d35f051e.aspx) which said that

"We can use the FocusManager.FocusedElement property to shift focus in XAML but it only works when the element exists in the main XAML file.  If you use UserControls it turns out that the approach doesn't work because that element is loaded separately and not available when the initial focus is being determined."

(I also found that the solution offered by the above article worked okay when the popup is opened the first time, but not subsequently).

S

 

Coordinator
Aug 9, 2010 at 2:45 PM
Ok. I believe your problem is due to the "scoping" of UserControl. I don't have any real recommendation based on my experience. You are probably going to have to come up with a custom solution for this. If you find an elegant way to do this, I'd love to hear about it. Sorry I couldn't be more help.
Aug 9, 2010 at 2:46 PM
Edited Aug 9, 2010 at 2:46 PM

OK. Thanks for you time, Rob

Aug 9, 2010 at 3:11 PM

Well. Don't know how elegant (and generic) this is ... but it appears to work!

a) I have a helper function (c/o http://www.codeproject.com/KB/WPF/CinchIII.aspx#PopServ)

 
/// <summary>
/// This class forces focus to set on the specified UIElement 
/// </summary>
public class FocusHelper
{
 
    /// <summary>
    /// Set focus to UIElement
    /// </summary>
    /// <param name="element">The element to set focus on</param>
 
    public static void Focus(UIElement element)
    {
        //Focus in a callback to run on another thread, ensuring the main 
        //UI thread is initialized by the time focus is set
        ThreadPool.QueueUserWorkItem(delegate(Object theElement)
        {
            UIElement elem = (UIElement)theElement;
            elem.Dispatcher.Invoke(DispatcherPriority.Normal,
                (Action)delegate()
                {
                    elem.Focus();
                    Keyboard.Focus(elem);
                });
        }, element);
    }
}

b) I have a method in the main window's view model

public void SetFocus(UIElement target)
{
	FocusHelper.Focus(target);
}

c) The main window XAML includes the trigger

        <Popup Name="localPopup">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Opened">
                    <cal:ActionMessage MethodName="SetFocus">
                        <cal:Parameter Value="{Binding ElementName=MyPopup}" />
                    </cal:ActionMessage>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        
            <local:PopupView x:Name="MyPopup" ></local:PopupView>
        </Popup>

I suspect that I can simplify this and remove the call thru the view model to the helper method directly ... but it (wpf mvvm etc etc) is all a bit new to me!

d) The child usercontrol needs to have the Focusable property set to true! I think that may have been the critical bit I was missing!

Simon

Oct 29, 2010 at 10:30 PM
Edited Oct 29, 2010 at 10:32 PM

I've just finished fighting through the same issue, except in a more general sense.  I wanted to be able to set focus from the view model, binding to a bool property (like IsEnabled).  I ended up hacking together a custom behavior like so:

public class FocusBehavior : Behavior<Control>
    {

        protected override void OnAttached()
        {
            AssociatedObject.GotFocus += (sender, args) => IsFocused = true;
            AssociatedObject.LostFocus += (sender, a) => IsFocused = false;
            AssociatedObject.Loaded += (o, a) => { if (HasInitialFocus || IsFocused) AssociatedObject.Focus(); };

            base.OnAttached();
        }

        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.Register(
                "IsFocused",
                typeof(bool),
                typeof(FocusBehavior),
                new PropertyMetadata(false, (d, e) => { if ((bool)e.NewValue) ((FocusBehavior)d).AssociatedObject.Focus(); }));

        public bool IsFocused
        {
            get { return (bool)GetValue(IsFocusedProperty); }
            set { SetValue(IsFocusedProperty, value); }
        }

        public static readonly DependencyProperty HasInitialFocusProperty =
            DependencyProperty.Register(
                "HasInitialFocus",
                typeof(bool),
                typeof(FocusBehavior),
                new PropertyMetadata(false, null));

        public bool HasInitialFocus
        {
            get { return (bool)GetValue(HasInitialFocusProperty); }
            set { SetValue(HasInitialFocusProperty, value); }
        }
    }

 

And use it in my view like so:

 

<TextBox x:Name="UserName" Style="{StaticResource LoginTextBox}">
  <i:Interaction.Behaviors>
    <localBehaviors:FocusBehavior HasInitialFocus="True" IsFocused="{Binding UserNameIsFocused, Mode=TwoWay}"/>
  </i:Interaction.Behaviors>
</TextBox>

My login view model, on unsuccessful login, can now focus back to the username textbox by setting the UserNameIsFocused property.

 

Dec 9, 2010 at 10:57 PM

dfaivre, I've found your FocusBehavior very usable.

Thanks, man!

Jun 7, 2013 at 6:42 AM
The FocusBehavior doesn't work for me. I get the exception "Cannot add instance of type 'FocusBehavior' to a collection of type 'BehaviorCollection'. Only items of type 'T' are allowed."