Search code examples
c#wpfmvvmtextboxmvvm-light

How to read TextBox focus from ViewModel - MVVM Light


I have two textBoxes and in my ViewModel I would like to be able to keep track of which box is currently in focus.

<TextBox x:Name="textBox1" Text="Text Box 1"/>
<TextBox x:Name="textBox2" Text="Text Box 2"/>

How can I read/identify which textBox is currently in focus from my ViewModel?


Solution

  • There are several ways how you can achieve this, some of them:

    1) Use behavior:

    • You need System.Windows.Interactivity.dll
    • Behavior (setting IsFocused property will not make element focused, you need slightly extend behavior in order to achieve this)

      public class FocusChangedBehavior : Behavior<UIElement>
      {
          public static readonly DependencyProperty IsFocusedProperty = 
             DependencyProperty.Register(
             nameof(IsFocused),
             typeof(bool),
             typeof(FocusChangedBehavior),
             new FrameworkPropertyMetadata(default(bool), 
                  FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
      
          public bool IsFocused
          {
              get { return (bool)this.GetValue(IsFocusedProperty); }
              set { this.SetValue(IsFocusedProperty, value); }
          }
      
          /// <inheritdoc />
          protected override void OnAttached()
          {
              this.AssociatedObject.GotFocus += this.AssociatedObjectFocused;
              this.AssociatedObject.LostFocus += this.AssociatedObjectUnfocused;
          }
      
          /// <inheritdoc />
          protected override void OnDetaching()
          {
              this.AssociatedObject.GotFocus -= this.AssociatedObjectFocused;
              this.AssociatedObject.LostFocus -= this.AssociatedObjectUnfocused;
          }
      
          private void AssociatedObjectFocused(object sender, RoutedEventArgs e)
          {
              this.IsFocused = true;
          }
      
          private void AssociatedObjectUnfocused(object sender, RoutedEventArgs e)
          {
              this.IsFocused = false;
          }
      }
      
    • In XAML you bind IsFocused to property in ViewModel.

      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

      <TextBox x:Name="textBox1" Text="Text Box 1">
          <i:Interaction.Behaviors>
              <local:FocusChangedBehavior IsFocused="{Binding IsFocusedTxt1}" />
          </i:Interaction.Behaviors>
      </TextBox>
      
      <TextBox x:Name="textBox2" Text="Text Box 2">
          <i:Interaction.Behaviors>
              <local:FocusChangedBehavior IsFocused="{Binding IsFocusedTxt2}" />
          </i:Interaction.Behaviors>
      </TextBox>
      
    • Finally in View-Model create properties

      public bool IsFocusedTxt1 { get; set; }
      
      public bool IsFocusedTxt2 { get; set; }
      



    2) Alternatively you could you use EventTrigger in the XAML

    • You need System.Windows.Interactivity.dll and MicrosoftExpressionInteractions (For the ActionCommand)
    • Event Triggers:

      <TextBox x:Name="textBox1" Text="Text Box 1">
          <i:Interaction.Triggers>
              <i:EventTrigger  EventName="GotFocus">
                  <i:InvokeCommandAction Command="{Binding NotifyFocusedReceivedTxt1Command}" />
              </i:EventTrigger>
          </i:Interaction.Triggers>
      </TextBox>
      
    • In ViewModel create command NotifyFocusedReceivedTxt1Command

      public ICommand NotifyFocusedReceivedTxt1Command { get; }
      
      // in constructor
      this.NotifyFocusedReceivedTxt1Command = new ActionCommand(this.FocusedReceivedTxt1);
      
      // and method
      private void FocusedReceivedTxt1()
      {
          // Your logic
      }
      
    • Also, if you don't want introduce many command/properties you could use same command and pass different textboxes by setting CommandParameter (slightly breaks MVVM, but not critically)

      <TextBox x:Name="textBox1" Text="Text Box 1">
          <i:Interaction.Triggers>
              <i:EventTrigger  EventName="GotFocus">
                  <i:InvokeCommandAction Command="{Binding NotifyFocusedReceivedCommand}" 
                                         CommandParameter="{Binding ., ElementName=textBox1}" />
              </i:EventTrigger>
          </i:Interaction.Triggers>
      </TextBox>
      
      <TextBox x:Name="textBox2" Text="Text Box 2">
          <i:Interaction.Triggers>
              <i:EventTrigger  EventName="GotFocus">
                  <i:InvokeCommandAction Command="{Binding NotifyFocusedReceivedCommand}" 
                                         CommandParameter="{Binding ., ElementName=textBox2}" />
              </i:EventTrigger>
          </i:Interaction.Triggers>
      </TextBox>
      

      and

      public ICommand NotifyFocusedReceivedCommand { get; }
      
      // in constructor
      this.NotifyFocusedReceivedCommand = new ActionCommand(this.FocusedReceived);
      
      // and method
      private void FocusedReceived(object control)
      {
          var txt = (TextBox)control;
          bool isFocused = txt.IsFocused;
          string name = txt.Name;
      }