UWP: The Master-Detail-Problem with the events SelectionChanged & LostFocus and How to Fix it

While creating apps with the Universal Windows Platform (UWP) I noticed a weird behavior, but I’m not sure if it’s a bug. Let’s just call it the “Master-Detail-Problem”.

To explain it, I stripped  down my code to the XAML-snippet below. No code-behind needed.

Just use this snippet in a blank UWP-App to see the problem:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition/>
  </Grid.ColumnDefinitions>
  <ListView x:Name="listView">
    <ListViewItem Content="Thomas"/>
    <ListViewItem Content="Microsoft"/>
  </ListView>
  <StackPanel Grid.Column="1">
    <TextBox 
      Text="{x:Bind listView.SelectedItem.(ListViewItem.Content), Mode=TwoWay}" Margin="10"/>
    <TextBox Text="Just to steal the focus" Margin="10"/>
  </StackPanel>
</Grid>

As you can see in the snippet above, a TextBox is bound in a TwoWay-Binding to the Content-Property of the SelectedItem of a ListView. Now some background on TwoWay-Data bindings:

The TextBox is updating the Binding-source when it is losing its focus. This happens in the LostFocus-event

For compiled data bindings (x:Bind) the event handler for the LostFocus-event is generated in the MainPage.g.cs-file that you find in the obj\x86-folder of your project.

For compiled data bindings there’s no UpdateSourceTrigger-property in the lastest UWP-version (Windows 10 Anniversary / Redstone 1) to change this “lost-focus” behavior. With a classical data binding you could set the UpdateSourceTrigger-Property of the data binding to PropertyChanged, so that the TextBox immediately updates the source after every change. But the default for a classical data binding is as well the “lost focus” behavior.

Ok, so far so good. The TextBox from the XAML-snippet above updates the source when it is losing its focus. Now let’s try this:

  1. Select the ListViewItem “Thomas”
  2. Change the Text in the bound TextBox from “Thomas” to “Thomas Claudius”
  3. Select the other TextBox to steal the focus

This works: The Content of the first ListViewItem changed as expected from “Thomas” to “Thomas Claudius”. Great. Now let’s look at another working thing before we look at the problem.

  1. Select the first ListViewItem “Thomas Claudius”
  2. Change the Text in the bound TextBox from “Thomas Claudius” back to “Thomas”
  3. Select the same ListViewItem “Thomas Claudius” by clicking on it.

This works as well: The Text changed back from “Thomas Claudius” to “Thomas”.

Now let’s look at the problem

Let’s do the same thing again, but instead of selecting the same ListViewItem after changing the Text, let’s select the other one, the second ListViewItem:

  1. Select the first ListViewItem “Thomas”
  2. Change the Text in the bound TextBox from “Thomas” to “Lara Croft”
  3. Select the second ListViewItem “Microsoft” by clicking on it

After you did this, you’ll notice that the content “Thomas” did not change to “Lara Croft”

The content stays at “Thomas”. When you select the ListViewItem “Thomas” again, you’ll see the Text “Thomas” as well in the TextBox. “Lara Croft” is no more there, it’s lost (or should I write here “she’s lost”? :-)). In my opinion the name “Lara Croft” should have been written to the Content-Property of the first ListViewItem, but it didn’t happen. But why?

What’s going on?

I debugged the compiled data binding code and found out the problem in UWP:

The problem in UWP is: The SelectedItem of the ListView is changed before the TextBox is losing its focus. And the TextBox will write the data back to the SelectedItem after it was losing its focus. But then the SelectedItem is already a different one

So what does this mean? Behind the scenes this is happening exactly:

  1. We select the first ListViewItem “Thomas”
  2. the bound TextBox displays the Text “Thomas”
  3. We click into the bound TextBox, it gets focused
  4. We change the Text to “Lara Croft”
  5. We select the second ListViewItem “Microsoft”

And now the interesting bit happens. Go and get your popcorn. :-)

  1. The ListView’s SelectionChanged-event is fired and the SelectedItem is changed
  2. The TextBox is losing its focus, just before the ListView gets the focus
  3. When the TextBox is losing its focus, it writes the Text back to the Content-Property of the SelectedItem in the ListView. But at this time, the Text in the TextBox is already “Microsoft”, as the SelectedItem has been changed before the Textbox has lost its focus.

So in the LostFocus-event handler for the TextBox – that you’ll find in the code for the compiled data binding in the obj/x86/MainPage.g.cs-file – you’ll see that the ListViewItem with the Content “Microsoft” gets the new content “Microsoft”. The selection has already changed before this code is executed. So the original and expected ListViewItem with the Content “Thomas” is never updated to “Lara Croft”.

How is WPF handling this?

I was really curious how WPF is doing this, as I never heard about this problem. So I have checked out WPF, and I noticed that WPF is doing it right:

  • In WPF the TextBox’s LostFocus-event is fired before the SelectionChanged-event
  • In UWP the TextBox’s LostFocus-event is fired after the SelectionChanged-event

How to fix it in UWP?

To fix this issue, we need to ensure that the LostFocus-event of the TextBox is fired before the SelectedItem in the ListView is changed.

All we need to do is to create a PointerPressed-EventHandler that is called for handled events too. In that PointerPressed-EventHandler we just focus the ListViewItem when it has been clicked. Sounds like a great task for an Attached Behavior by using an Attached Property. Here we go:

/// <summary>
/// Ensures that the ListViewItem is focused before
/// the SelectedItem of the parent ListView is changed
/// See more here: 
/// (C) Thomas Claudius Huber 2016
/// http://www.thomasclaudiushuber.com
/// </summary>
public class ListViewService
{
  private static PointerEventHandler pointerEventHandler;
  public static readonly DependencyProperty FocusBeforeSelectProperty;

  static ListViewService()
  {
    pointerEventHandler = new PointerEventHandler(OnPointerPressed);
    FocusBeforeSelectProperty =
      DependencyProperty.RegisterAttached("FocusBeforeSelect", typeof(bool), typeof(ListViewService),
        new PropertyMetadata(false, OnPropertyChanged));
  }

  public static bool GetFocusBeforeSelect(ListView listView)
  {
    return (bool)listView.GetValue(FocusBeforeSelectProperty);
  }

  public static void SetFocusBeforeSelect(ListView listView, bool value)
  {
    listView.SetValue(FocusBeforeSelectProperty, value);
  }

  private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    var listView = d as ListView;
    if (listView != null)
    {
      listView.RemoveHandler(UIElement.PointerPressedEvent,
          pointerEventHandler);

      if ((bool)e.NewValue)
      {
        listView.AddHandler(UIElement.PointerPressedEvent,
          pointerEventHandler, true);
      }
    }
  }

  private static void OnPointerPressed(object sender, PointerRoutedEventArgs e)
  {
    ListView listView = (ListView)sender;
    ListViewItem listViewItem = GetListViewItem(e.OriginalSource);

    if (listViewItem != null)
    {
      listViewItem.Focus(FocusState.Programmatic);
    }
  }

  private static ListViewItem GetListViewItem(object originalSource)
  {
    ListViewItem listViewItem = null;
    var dp = originalSource as DependencyObject;
    while (dp != null)
    {
      listViewItem = dp as ListViewItem;
      if (listViewItem != null)
      {
        break;
      }
      dp = VisualTreeHelper.GetParent(dp);
    }

    return listViewItem;
  }
}

As you can see, the Attached-Property above adds an event handler for the PointerPressed-Event of the ListView. The last parameter passed to the AddHandler-method (line 42) is true, which means that the handler is also called for those events that have been marked as handled before.

In the handler itself, the ListViewItem is grabbed and on that ListViewItem the Focus-method is called. That’s it! Now we just need to use this attached property on our ListView

<ListView local:ListViewService.FocusBeforeSelect="True" x:Name="listView">

That’s it. Now when we run this, the TextBox will lose its focus before the SelectedItem of the ListView is changed. So the correct selected item is updated in the TextBox’s LostFocus-event handler that has been generated by the compiled data binding (x:Bind).

Share this post

Comments (6)

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.