Set focus on TextBox in WPF from view model

127,862

Solution 1

Let me answer to your question in three parts.

  1. I'm wondering what is "cs.txtCompanyID" in your example? Is it a TextBox control? If yes, then you are on a wrong way. Generally speaking it's not a good idea to have any reference to UI in your ViewModel. You can ask "Why?" but this is another question to post on Stackoverflow :).

  2. The best way to track down issues with Focus is... debugging .Net source code. No kidding. It saved me a lot of time many times. To enable .net source code debugging refer to Shawn Bruke's blog.

  3. Finally, general approach that I use to set focus from ViewModel is Attached Properties. I wrote very simple attached property, which can be set on any UIElement. And it can be bound to ViewModel's property "IsFocused" for example. Here it is:

    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);
        }
    
        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
                "IsFocused", typeof (bool), typeof (FocusExtension),
                new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
    
        private static void OnIsFocusedPropertyChanged(
            DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            var uie = (UIElement) d;
            if ((bool) e.NewValue)
            {
                uie.Focus(); // Don't care about false values.
            }
        }
    }
    

    Now in your View (in XAML) you can bind this property to your ViewModel:

    <TextBox local:FocusExtension.IsFocused="{Binding IsUserNameFocused}" />
    

Hope this helps :). If it doesn't refer to the answer #2.

Cheers.

Solution 2

I know this question has been answered a thousand times over by now, but I made some edits to Anvaka's contribution that I think will help others that had similar issues that I had.

Firstly, I changed the above Attached Property like so:

public static class FocusExtension
{
    public static readonly DependencyProperty IsFocusedProperty = 
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?), typeof(FocusExtension), new FrameworkPropertyMetadata(IsFocusedChanged){BindsTwoWayByDefault = true});

    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        return (bool?)element.GetValue(IsFocusedProperty);
    }

    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        element.SetValue(IsFocusedProperty, value);
    }

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

        if (e.OldValue == null)
        {
            fe.GotFocus += FrameworkElement_GotFocus;
            fe.LostFocus += FrameworkElement_LostFocus;
        }

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

        if (e.NewValue != null && (bool)e.NewValue)
        {
            fe.Focus();
        }
    }

    private static void fe_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)sender;
        if (fe.IsVisible && (bool)fe.GetValue(IsFocusedProperty))
        {
            fe.IsVisibleChanged -= fe_IsVisibleChanged;
            fe.Focus();
        }
    }

    private static void FrameworkElement_GotFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, true);
    }

    private static void FrameworkElement_LostFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, false);
    }
}

My reason for adding the visibility references were tabs. Apparently if you used the attached property on any other tab outside of the initially visible tab, the attached property didn't work until you manually focused the control.

The other obstacle was creating a more elegant way of resetting the underlying property to false when it lost focus. That's where the lost focus events came in.

<TextBox            
    Text="{Binding Description}"
    FocusExtension.IsFocused="{Binding IsFocused}"/>

If there's a better way to handle the visibility issue, please let me know.

Note: Thanks to Apfelkuacha for the suggestion of putting the BindsTwoWayByDefault in the DependencyProperty. I had done that long ago in my own code, but never updated this post. The Mode=TwoWay is no longer necessary in the WPF code due to this change.

Solution 3

I think the best way is to keep the MVVM principle clean, so basically you must use the Messenger Class provided with the MVVM Light and here is how to use it:

in your viewmodel(exampleViewModel.cs):write the following

 Messenger.Default.Send<string>("focus", "DoFocus");

now in your View.cs(not the XAML the view.xaml.cs) write the following in the constructor

 public MyView()
        {
            InitializeComponent();

            Messenger.Default.Register<string>(this, "DoFocus", doFocus);
        }
        public void doFocus(string msg)
        {
            if (msg == "focus")
                this.txtcode.Focus();
        }

that method owrks just fine and with less code and maintaining MVVM standards

Solution 4

None of these worked for me exactly, but for the benefit of others, this is what I ended up writing based on some of the code already provided here.

Usage would be as follows:

<TextBox ... h:FocusBehavior.IsFocused="True"/>

And the implementation would be as follows:

/// <summary>
/// Behavior allowing to put focus on element from the view model in a MVVM implementation.
/// </summary>
public static class FocusBehavior
{
    #region Dependency Properties
    /// <summary>
    /// <c>IsFocused</c> dependency property.
    /// </summary>
    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?),
            typeof(FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
    /// <summary>
    /// Gets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>Value of the <c>IsFocused</c> property or <c>null</c> if not set.</returns>
    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        return (bool?)element.GetValue(IsFocusedProperty);
    }
    /// <summary>
    /// Sets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        element.SetValue(IsFocusedProperty, value);
    }
    #endregion Dependency Properties

    #region Event Handlers
    /// <summary>
    /// Determines whether the value of the dependency property <c>IsFocused</c> has change.
    /// </summary>
    /// <param name="d">The dependency object.</param>
    /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = d as FrameworkElement;
        if (fe != null && e.OldValue == null && e.NewValue != null && (bool)e.NewValue)
        {
            // Attach to the Loaded event to set the focus there. If we do it here it will
            // be overridden by the view rendering the framework element.
            fe.Loaded += FrameworkElementLoaded;
        }
    }
    /// <summary>
    /// Sets the focus when the framework element is loaded and ready to receive input.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
    private static void FrameworkElementLoaded(object sender, RoutedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = sender as FrameworkElement;
        if (fe != null)
        {
            // Remove the event handler registration.
            fe.Loaded -= FrameworkElementLoaded;
            // Set the focus to the given framework element.
            fe.Focus();
            // Determine if it is a text box like element.
            var tb = fe as TextBoxBase;
            if (tb != null)
            {
                // Select all text to be ready for replacement.
                tb.SelectAll();
            }
        }
    }
    #endregion Event Handlers
}

Solution 5

This is an old thread, but there doesn't seem to be an answer with code that addresses the issues with Anavanka's accepted answer: it doesn't work if you set the property in the viewmodel to false, or if you set your property to true, the user manually clicks on something else, and then you set it to true again. I couldn't get Zamotic's solution to work reliably in these cases either.

Pulling together some of the discussions above gives me the code below which does address these issues I think:

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

    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached(
         "IsFocused", typeof(bool), typeof(FocusExtension),
         new UIPropertyMetadata(false, null, OnCoerceValue));

    private static object OnCoerceValue(DependencyObject d, object baseValue)
    {
        if ((bool)baseValue)
            ((UIElement)d).Focus();
        else if (((UIElement) d).IsFocused)
            Keyboard.ClearFocus();
        return ((bool)baseValue);
    }
}

Having said that, this is still complex for something that can be done in one line in codebehind, and CoerceValue isn't really meant to be used in this way, so maybe codebehind is the way to go.

Share:
127,862
priyanka.sarkar
Author by

priyanka.sarkar

Student

Updated on January 06, 2021

Comments

  • priyanka.sarkar
    priyanka.sarkar over 3 years

    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}"/>
    
    • matze8426
      matze8426 almost 6 years
      When you are using caliburn.micro this is an excellent solution.
  • Sam
    Sam about 14 years
    Cool idea. I need to set IsUserNameFocused to true, then false again to get this working, is this right?
  • Anvaka
    Anvaka about 14 years
    Thank you Sam. It depends. Sometimes it's enough to set it to true only once.
  • Josh G
    Josh G almost 13 years
    OP is using WPF. Focus code for WinForms is not going to help.
  • Rob
    Rob almost 13 years
    @Anvaka how do you do the binding in your view model? is it a property or an ICommand or somthing else, I can't seem to get my head around this bit.
  • Anvaka
    Anvaka almost 13 years
    @Rob it's just a property, named "IsUserNameFocused".
  • Hiren Gondaliya
    Hiren Gondaliya over 12 years
    Why have you a if statement? the _isFocused once set to false will just be changed to value on the next line.
  • Rachel
    Rachel over 12 years
    You should also call Keyboard.Focus(uie); from your OnIsFocusedPropertyChanged event if you want your control to receive Keyboard Focus as well as Logical Focus
  • RichardOD
    RichardOD about 12 years
    @Tyrsius You can round this issue by getting the dependency property to Coerce, see here- social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
  • Ε Г И І И О
    Ε Г И І И О about 12 years
    Well if you want to keep the MVVM principle clean, you wouldn't be writing code in your code behind in the first place. I believe the attached property approach is much cleaner. It doesn't introduce a lot of magic strings in your view model as well.
  • Admin
    Admin over 11 years
    I chhanged "object fe = (FrameworkElement)d;" to "FrameworkElement fe = (FrameworkElement)d;" so the intellisense works
  • user3245801
    user3245801 about 11 years
    El Nino: Where exactly did you get the idea there shouldn't be anything in your view code-behind? Anything that is UI-related should be in the view's code-behind. Setting focus of UI elements should Definitely be in the view's code-behind. Let the viewmodel figure out when to send the message; let the view figure out what to do with the message. That is what M-V-VM does: separates the concerns of data model, business logic, and UI.
  • ygoe
    ygoe about 11 years
    How is this supposed to be used? If I set my property to true, the control is focused. But it will always be focused again when I come back to this view. Resetting it from OnIsFocusedPropertyChanged doesn't change this. Resetting it directly after setting it from the ViewModel does not focus anything anymore. It doesn't work. What have those 70 upvoters done exactly?
  • ygoe
    ygoe about 11 years
    Still doesn't solve the problem. The element stays focused every time I come back to it.
  • HelloSam
    HelloSam about 11 years
    This works well for me except I need to add a "if (e.Source == e.OriginalSource)" check in the GotFocus/LostFocus or else it stackoverflows (literally) when used on my UserControl, which does redirect the focus to inner component. I removed the Visible checks, accepting the fact that it works just like .Focus() method. If .Focus() doesn't work, the binding shouldn't work - and that's ok for my scenario.
  • WiiMaxx
    WiiMaxx almost 11 years
    @Anvaka i tryed something based on this solution but i got stucked please take a look at it Click HERE
  • Tore Aurstad
    Tore Aurstad almost 11 years
    I wrestled with setting the focus of a textbox in a dialog I display in a WPF application displaying the dialog as a UserControl. I managed to finally make the focusing working, after trying out many different approaches. Your code above worked after I adjusted it to use Dispatcher.CurrentDispatcher.BeginInvoke to do the Focus call: Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() => { uie.Focus(); //Don't care about false values }));
  • Owen Johnson
    Owen Johnson over 10 years
    you can change obj.SetValue(IsFocusedProperty, value); to obj.SetValue(IsFocusedProperty, false); and not have to set false and true again.
  • Simon D.
    Simon D. over 10 years
    I also changed the callback to this: ...if ((bool)e.NewValue && uie.Dispatcher != null) { uie.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() => uie.Focus())); // invoke behaves nicer, if e.g. you have some additional handler attached to 'GotFocus' of UIE. uie.SetValue(IsFocusedProperty, false); // reset bound value if possible, to allow setting again ... Sometimes I even have to reset the 'IsFocused' to false in the ViewModel, if I want to set the focus multiple times. But then it works, where some other methods failed.
  • ygoe
    ygoe over 10 years
    Based on this suggestion, I've implemented my own ViewCommandManager that handles invoking commands in connected views. It's basically the other direction of regular Commands, for these cases when a ViewModel needs to do some action in its View(s). It uses reflection like data-bound commands and WeakReferences to avoid memory leaks. dev.unclassified.de/source/viewcommand (also on CodeProject)
  • Gordon Slysz
    Gordon Slysz about 10 years
    I used this method to print out WPF FlowDocuments. Worked nicely. Thanks
  • Contango
    Contango over 9 years
    This worked beautifully for me. I'm using Visual Studio 2013 / MVVM Light. To effectively remove the visible focus from all textboxes, you can set the focus to something like a TextBlock which isn't normally editable.
  • Asheh
    Asheh over 9 years
    YOU ABSOLUTE GENIUS. Spent a whole day on this. Thanks!! I have been trying to find a way to focus an element and hide it when it doesn't have focus. But this really screws up the dependency properties. This solution worked for me.
  • Olaru Mircea
    Olaru Mircea about 9 years
    I am using this in WF 4.5. On IsFocusedChanged i have a scenario (an Activity gets reloaded ) where e.NewValue is null and throws a exception so check that first. Everything works fine with this minor change.
  • R00st3r
    R00st3r over 7 years
    Thanks this wprks Great :) I just added ' {BindsTwoWayByDefault = true}' at 'FrameworkPropertyMetadata' to set the default mode to TwoWayBinding so it is not needed on every Binding
  • NathanAldenSr
    NathanAldenSr over 7 years
    This works consistently, whereas the accepted answer does not. Thanks!
  • Bigeyes
    Bigeyes over 7 years
    I want one in Silverlight? Can we use it?
  • M3SSYM4RV1N
    M3SSYM4RV1N over 6 years
    Still an amazing solution 8 years later. Thanks!
  • scsfdev
    scsfdev over 6 years
    Although it is good to know how to make use of dependency injection from view model, I think sending out the message (this solution) is more straight forward and easy.
  • Mark Olbert
    Mark Olbert about 6 years
    I realize this is an old answer, but I'm running into a situation where the IsEnabled property of the control I want to shift the focus to is tied to a multi-value converter. Apparently, the GotFocus event handler gets called before the multi-value converter does...which means the control, at that point, is disabled, so as soon as GotFocus completes, LostFocus gets called (I guess because the control is still disabled). Any thoughts on how to handle that?
  • walterhuang
    walterhuang almost 6 years
    after you set the focus and another control gets the focus, to set the focus again won't work because IsFocused is still true. Need to force it to false and then true. public bool IsFocused { get { return _isFocused; } set { if (_isFocused == value) { _isFocused = false; OnPropertyChanged(); } _isFocused = value; OnPropertyChanged(); } }
  • Michael K
    Michael K over 5 years
    I used this in a pure MVVM solution. Works great, thanks
  • Apfelkuacha
    Apfelkuacha over 5 years
    @MarkOlbert use fe.Dispatcher.BeginInvoke(new Action(() => { fe.Focus(); }), DispatcherPriority.Loaded); that it is updated after it is loaded. More info here: telerik.com/forums/isfocused-property#OXgFYZFOg0WZ2rxidln61Q
  • Daniel Žeimo
    Daniel Žeimo over 5 years
    Very good solution, helped a lot. Only problem that I had was when I returned from one window to another it threw null exception so I added check for null in if (e.NewValue != null && (bool)e.NewValue) and now works well
  • Joshua Frank
    Joshua Frank over 4 years
    This doesn't work for me. When I try it, the control gets the focus but doesn't show the caret. Or maybe it's losing the focus and not showing it for that reason, but either way the focus doesn't go to the right place.
  • Welcor
    Welcor over 4 years
    this solution is easier to use in the viewModel. Just set the bound property to true once. after that reusing Notify property change on the property will get it into focus again. The accepted answer instead would need set true -> notify -> set false -> notify every time, which is quite annoying. the more i have to work with wpf mvvm, the more i love the time with win forms and code behind... single line solutions where so awesome..
  • Welcor
    Welcor over 4 years
    while this works better in viewModel, it crashes when comboBox with isEditable=true is used. the accepted answer does not crash in this case
  • user2430797
    user2430797 about 4 years
    I like this. This works well if you want to set the initial focus.
  • Marinpietri
    Marinpietri almost 2 years
    Thks so much !!! it's works fine