Set focus on TextBox in WPF from view model

Viewed 130321

I have a TextBox and a Button in my view.

Now I am checking a condition upon button click and if the condition turns out to be false, displaying the message to the user, and then I have to set the cursor to the TextBox control.

if (companyref == null)
{
    var cs = new Lipper.Nelson.AdminClient.Main.Views.ContactPanels.CompanyAssociation(); 

    MessageBox.Show("Company does not exist.", "Error", MessageBoxButton.OK,
                    MessageBoxImage.Exclamation);

    cs.txtCompanyID.Focusable = true;

    System.Windows.Input.Keyboard.Focus(cs.txtCompanyID);
}

The above code is in the ViewModel.

The CompanyAssociation is the view name.

But the cursor is not getting set in the TextBox.

The xaml is:

<igEditors:XamTextEditor Name="txtCompanyID" 
                         KeyDown="xamTextEditorAllowOnlyNumeric_KeyDown"
                         ValueChanged="txtCompanyID_ValueChanged"
                         Text="{Binding Company.CompanyId,
                                        Mode=TwoWay,
                                        UpdateSourceTrigger=PropertyChanged}"
                         Width="{Binding ActualWidth, ElementName=border}"
                         Grid.Column="1" Grid.Row="0"
                         VerticalAlignment="Top"
                         HorizontalAlignment="Stretch"
                         Margin="0,5,0,0"
                         IsEnabled="{Binding Path=IsEditable}"/>

<Button Template="{StaticResource buttonTemp1}"
        Command="{Binding ContactCommand}"
        CommandParameter="searchCompany"
        Content="Search"
        Width="80"
        Grid.Row="0" Grid.Column="2"
        VerticalAlignment="Top"
        Margin="0"
        HorizontalAlignment="Left"
        IsEnabled="{Binding Path=IsEditable}"/>
21 Answers

An alternative approach based on @Sheridan answer here

 <TextBox Text="{Binding SomeText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <TextBox.Style>
            <Style>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding SomeTextIsFocused, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
                        <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>

In your view model set up your binding in the usual way and then set the SomeTextIsFocused to true to set the focus on your text box

For Silverlight:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace MyProject.Behaviors
{
    public class FocusBehavior : Behavior<Control>
    {
        protected override void OnAttached()
        {
            this.AssociatedObject.Loaded += AssociatedObject_Loaded;
            base.OnAttached();
        }

        private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
            if (this.HasInitialFocus || this.IsFocused)
            {
                this.GotFocus();
            }
        }

        private void GotFocus()
        {
            this.AssociatedObject.Focus();
            if (this.IsSelectAll)
            {
                if (this.AssociatedObject is TextBox)
                {
                    (this.AssociatedObject as TextBox).SelectAll();
                }
                else if (this.AssociatedObject is PasswordBox)
                {
                    (this.AssociatedObject as PasswordBox).SelectAll();
                }
                else if (this.AssociatedObject is RichTextBox)
                {
                    (this.AssociatedObject as RichTextBox).SelectAll();
                }
            }
        }

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

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

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

        public bool IsSelectAll
        {
            get { return (bool)GetValue(IsSelectAllProperty); }
            set { SetValue(IsSelectAllProperty, value); }
        }

    }
}

LoginViewModel.cs:

    public class LoginModel : ViewModelBase
    {
        ....

        private bool _EmailFocus = false;
        public bool EmailFocus
        {
            get
            {
                return _EmailFocus;
            }
            set
            {
                if (value)
                {
                    _EmailFocus = false;
                    RaisePropertyChanged("EmailFocus");
                }
                _EmailFocus = value;
                RaisePropertyChanged("EmailFocus");
            }
        }
       ...
   }

Login.xaml:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:MyProject.Behaviors"

<TextBox Text="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <i:Interaction.Behaviors>
        <beh:FocusBehavior IsFocused="{Binding EmailFocus}" IsSelectAll="True"/>
    </i:Interaction.Behaviors>
</TextBox>

OR

<TextBox Text="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <i:Interaction.Behaviors>
        <beh:FocusBehavior HasInitialFocus="True" IsSelectAll="True"/>
    </i:Interaction.Behaviors>
</TextBox>

To set the focus should just do it in code:

EmailFocus = true;

Remember that this plugin is part of an html page, so other controls in the page might have the focus

if (!Application.Current.IsRunningOutOfBrowser)
{
    System.Windows.Browser.HtmlPage.Plugin.Focus();
}

First off i would like to thank Avanka for helping me solve my focus problem. There is however a bug in the code he posted, namely in the line: if (e.OldValue == null)

The problem I had was that if you first click in your view and focus the control, e.oldValue is no longer null. Then when you set the variable to focus the control for the first time, this results in the lostfocus and gotfocus handlers not being set. My solution to this was as follows:

public static class ExtensionFocus
    {
    static ExtensionFocus()
        {
        BoundElements = new List<string>();
        }

    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?),
        typeof(ExtensionFocus), new FrameworkPropertyMetadata(false, IsFocusedChanged));

    private static List<string> BoundElements;

    public static bool? GetIsFocused(DependencyObject element)
        {
        if (element == null)
            {
            throw new ArgumentNullException("ExtensionFocus GetIsFocused called with null element");
            }
        return (bool?)element.GetValue(IsFocusedProperty);
        }

    public static void SetIsFocused(DependencyObject element, bool? value)
        {
        if (element == null)
            {
            throw new ArgumentNullException("ExtensionFocus SetIsFocused called with null element");
            }
        element.SetValue(IsFocusedProperty, value);
        }

    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        var fe = (FrameworkElement)d;

        // OLD LINE:
        // if (e.OldValue == null)
        // TWO NEW LINES:
        if (BoundElements.Contains(fe.Name) == false)
            {
            BoundElements.Add(fe.Name);
            fe.LostFocus += OnLostFocus;
            fe.GotFocus += OnGotFocus;
            }           


        if (!fe.IsVisible)
            {
            fe.IsVisibleChanged += new DependencyPropertyChangedEventHandler(fe_IsVisibleChanged);
            }

        if ((bool)e.NewValue)
            {
            fe.Focus();             
            }
        }

    private static void fe_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
        var fe = (FrameworkElement)sender;

        if (fe.IsVisible && (bool)((FrameworkElement)sender).GetValue(IsFocusedProperty))
            {
            fe.IsVisibleChanged -= fe_IsVisibleChanged;
            fe.Focus();
            }
        }

    private static void OnLostFocus(object sender, RoutedEventArgs e)
        {
        if (sender != null && sender is Control s)
            {
            s.SetValue(IsFocusedProperty, false);
            }
        }

    private static void OnGotFocus(object sender, RoutedEventArgs e)
        {
        if (sender != null && sender is Control s)
            {
            s.SetValue(IsFocusedProperty, true);
            }
        }
    }

After implementing the accepted answer I did run across an issue that when navigating views with Prism the TextBox would still not get focus. A minor change to the PropertyChanged handler resolved it

    private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var uie = (UIElement)d;
        if ((bool)e.NewValue)
        {
            uie.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
            {
                uie.Focus();
            }));
        }
    }

Just do this:

<Window x:class...
   ...
   ...
   FocusManager.FocusedElement="{Binding ElementName=myTextBox}"
>
<Grid>
<TextBox Name="myTextBox"/>
...
Related