none
Managing focus while playing nice with MVVM

    Question

  • I have an application I'm working on that iterates through bills and displays fields that users need to "work" on.  See the link below for a sample project I'll be referencing in this post.

    Here are the business rules:

    1. When a bill is loaded the first field that needs verified (red background) is selected
    2. A user can navigate the fields by using the [Up]/[Down] arrow keys
    3. When a user is in a field and hits [Enter] that field will be set verified (green background) and the next field that requires verification is navigated to
    4. When a field is navigated to (via [Enter], arrow keys, or for a new bill being loaded) all the field content is highlighted
    5. When a user has verified (via [Enter]) all the fields (no red background), the Submit button is focused.
    6. Hitting [Enter] again on the Submit button loads the next bill and we start again at #1, with the first field needing verified being selected

    I got focus management to work for the arrow keys and Enter key working by using a ListBox as the field container and managing the selected item from the view model via the BillViewModel.CurrentField property.  The [Up]/[Down]/[Enter] keys are bound to commands on the BillViewModel that change the CurrentField property.  

    I also made sure the text is selected by adding events to the BillView for ListBox.SelectionChanged and ListBox.Loaded that would give focus to the input field (TextBox) of the ListBoxItem.  Then, I have handlers for TextBox.Focus that selects all the text.  

    ***BONUS: If someone can help me out with a way to handle that stuff in XAML, that would be great!

    The problem I have is that I can't seem to find the right way to get focus back from the Submit button to the correct field after a new bill is loaded.  Instead, I either get stuck with the focus never leaving the Submit button or odd side effects.  For example, I tried using a RoutedCommand to fire the command of a the submit button and then use Keyboard.Focus(_FieldList) but it just caused the button to often times be disabled.  I tried adding an event handler for Button.Click but that fired before the command executed so that is no help.  The view model has no reference to the view (as it should be!) so I can't put code at the end of the submit command's execution to set focus on the appropriate control.  I'm stuck!

    In the solution linked below I don't have code to try to address this because it was all a mess and I think the community would be better served looking at a clean solution to find a way to help me out.

    I am trying to adhere to MVVM where possible.  

    Solution Link:

    http://cid-2712b8ee8d9edeff.office.live.com/self.aspx/Public/ManagingFocus.zip

     

    Thanks!

    Tuesday, April 26, 2011 7:02 PM

Answers

  • A simple model with the attached property, just move the focus when we click the Button.

        <StackPanel>
          <TextBox x:Name="textbox" Text="Text 1" Margin="2" BorderBrush="Blue">
          </TextBox>
          <Button Content="Submit" Margin="2" x:Name="submit" Command="{Binding ButtonCommand}" 
              local:ButtonHelper.IsClicked="{Binding IsClicked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
              local:ButtonHelper.NextFocusElement="{Binding ElementName=textbox}"/>
        </StackPanel>
    

     

    ButtonHelper class:

      public class ButtonHelper : DependencyObject
      {
        public static bool GetIsClicked(DependencyObject obj)
        {
          return (bool)obj.GetValue(IsClickedProperty);
        }
        public static void SetIsClicked(DependencyObject obj, bool value)
        {
          obj.SetValue(IsClickedProperty, value);
        }
        public static readonly DependencyProperty IsClickedProperty =
          DependencyProperty.RegisterAttached("IsClicked", typeof(bool), typeof(ButtonHelper), new UIPropertyMetadata(false,
            (o, e) =>
            {
              if ((bool)e.NewValue == true)
              {
                ButtonHelper.GetNextFocusElement(o).Focus();
                (o as DependencyObject).SetValue(ButtonHelper.IsClickedProperty, false);
              }
            }));
    
        public static UIElement GetNextFocusElement(DependencyObject obj)
        {
          return (UIElement)obj.GetValue(NextFocusElementProperty);
        }
        public static void SetNextFocusElement(DependencyObject obj, UIElement value)
        {
          obj.SetValue(NextFocusElementProperty, value);
        }
        public static readonly DependencyProperty NextFocusElementProperty =
          DependencyProperty.RegisterAttached("NextFocusElement", typeof(UIElement), typeof(ButtonHelper), new UIPropertyMetadata(null));
    
      }
    

     

    The entire sample download: http://cid-51b2fdd068799d15.office.live.com/self.aspx/.Public/Samples%5E_2011/20110428%5E_MoveFocusWhileButtonIsClicked.zip

     

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, April 28, 2011 8:24 AM
    Moderator

All replies

  • Hi bojingo,

    In the behind code, you could invoke the FocusManager to set he logical focus on the specific element: FocusManager.SetFocusedElement or you could in XAML to set the logical focus by the attached property: FocusManager.FocusedElement

    And for you question, you could implement an attached property on the Submit Button, meanwhile, you could add a property to mark if the Button is clicked. Then bind the attached property to this IsClicked property, in the call back of the attached property, you could awtich the focus.

    Regarding to the ***BONUS, it is hard to move the complex logic into XAML, but you could refer to use the attached command on event:

    handle the logic in the command while the event is firing.

     

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, April 27, 2011 5:16 AM
    Moderator
  • In the behind code, you could invoke the FocusManager to set he logical focus on the specific element: FocusManager.SetFocusedElement or you could in XAML to set the logical focus by the attached property: FocusManager.FocusedElement

    And for you question, you could implement an attached property on the Submit Button, meanwhile, you could add a property to mark if the Button is clicked. Then bind the attached property to this IsClicked property, in the call back of the attached property, you could awtich the focus.

    Yes, I know about FocusManager, I just don't know when to call it, from where, and what is the best mechanism to use it.  For example, I'd love to do it in XAML, but where would I put said XAML?

    I'm a little confused about your described solution using an attached property on the Submit button.  I think I need an example.  What would trigger the IsClicked attach property to switch it's value?  Would I have an event handler on the button click to set the attached property?  If so, how would that help considering the Submit button's command fires after the event handler.  My guess would be the logic would flow like this:

    1. User clicks the Submit button -> Click event fires
    2. Click event handlers sets the IsClicked attached property to true
    3. Property changed callback for the IsClicked attached property fires, giving focus to an item in the list - but wait, all the items are already verified since they haven't been submitted!
    4. Submit button's command is fired submitting the current bill and loading the next one

    Actually, after typing out the list, I don't understand the use of the attached property at all - because all I described was just a convoluted event handler!

     

    Regarding the ***BONUS, I'll look into it.  

    Thanks!

    Wednesday, April 27, 2011 2:54 PM
  • Control of focus is a view thing mate.

    So either aim to minimise code behind or abandon MVVM entirely.

    http://stackoverflow.com/questions/1178449/wpf-mvvm-focus-field-on-load

    Wednesday, April 27, 2011 4:00 PM
  • Andy,

    That is an oversimplification.  I'm not arguing against that the actual act of setting focus is a view responsibility.

    However, there are business rules that have to be addressed.  The business rule in my case is that when a bill is loaded the first field for that bill that needs verified should have focus.  After verifying said field, focus should immediately move to the next field that needs verified.  For these business rule to be testable I have to have it in the ViewModel.  

    The ViewModel is correctly designating the correct item to be selected and the ListBox is getting updated correctly.  All that is working correctly already.  But how can I, after the command of a button is invoked, set focus to the list box item that is already flagged to have focus (already flagged because it is the SelectedItem of the ListBox - binding is taking care of that for me).

    Your link doesn't help me at all because I'm not trying to set focus on load; instead I need to set focus after a button is click and after also running the command the button is responsible for.  

    So, ignoring MVVM entirely, what is a solution for my problem?

    I think this is a case where I convoluted the question by including too many details...

    • Edited by bojingo Wednesday, April 27, 2011 9:27 PM Formatting
    Wednesday, April 27, 2011 9:27 PM
  • A simple model with the attached property, just move the focus when we click the Button.

        <StackPanel>
          <TextBox x:Name="textbox" Text="Text 1" Margin="2" BorderBrush="Blue">
          </TextBox>
          <Button Content="Submit" Margin="2" x:Name="submit" Command="{Binding ButtonCommand}" 
              local:ButtonHelper.IsClicked="{Binding IsClicked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
              local:ButtonHelper.NextFocusElement="{Binding ElementName=textbox}"/>
        </StackPanel>
    

     

    ButtonHelper class:

      public class ButtonHelper : DependencyObject
      {
        public static bool GetIsClicked(DependencyObject obj)
        {
          return (bool)obj.GetValue(IsClickedProperty);
        }
        public static void SetIsClicked(DependencyObject obj, bool value)
        {
          obj.SetValue(IsClickedProperty, value);
        }
        public static readonly DependencyProperty IsClickedProperty =
          DependencyProperty.RegisterAttached("IsClicked", typeof(bool), typeof(ButtonHelper), new UIPropertyMetadata(false,
            (o, e) =>
            {
              if ((bool)e.NewValue == true)
              {
                ButtonHelper.GetNextFocusElement(o).Focus();
                (o as DependencyObject).SetValue(ButtonHelper.IsClickedProperty, false);
              }
            }));
    
        public static UIElement GetNextFocusElement(DependencyObject obj)
        {
          return (UIElement)obj.GetValue(NextFocusElementProperty);
        }
        public static void SetNextFocusElement(DependencyObject obj, UIElement value)
        {
          obj.SetValue(NextFocusElementProperty, value);
        }
        public static readonly DependencyProperty NextFocusElementProperty =
          DependencyProperty.RegisterAttached("NextFocusElement", typeof(UIElement), typeof(ButtonHelper), new UIPropertyMetadata(null));
    
      }
    

     

    The entire sample download: http://cid-51b2fdd068799d15.office.live.com/self.aspx/.Public/Samples%5E_2011/20110428%5E_MoveFocusWhileButtonIsClicked.zip

     

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, April 28, 2011 8:24 AM
    Moderator
  • The idea of the link was to show people's opinions on how to handle this kind of thing rather than code to cut and paste.  The consensus seems to be stick some code in the code behind and try and limit it to view manipulation.

    I think Bob's code looks pretty good and I can imagine the viewmodel exposing some NextFocusElement property which it sets using whatever business logic you have.

    This does break some people's idead on MVVM as there's code in the code behind.

    I think it's best to aim for minimising code behind rather than completely avoiding it though.

    Thursday, April 28, 2011 10:23 AM