'ListView SelectedItem not highlighted when set in ViewModel

I have a ListView with a ItemSource data binding and a SelectedItem data binding.

The ListView is populated with a new ItemSource every time I press the Next or Previous button.

The SelectedItem is updated accordingly, the items in the ItemSource have the Selected state, so it can be remembered when the user navigates back and forth.

While debugging, everything seems to work perfectly. The VM updates the controls as expected, and I can also see that the ListView has the correct selected value when I navigate with the next and previous buttons.

The problem is, that regardless of the fact that the ListView has a correct SelectedItem, the ListView does not visualize the SelectedItem as highlighted.

XAML:

<ListView 
    x:Name="_matchingTvShowsFromOnlineDatabaseListView" 
    Grid.Row="0" 
    Grid.Column="0"
    Grid.RowSpan="3"
    ItemsSource="{Binding AvailableMatchingTvShows}"
    SelectedItem="{Binding AcceptedMatchingTvShow, Mode=TwoWay}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Behaviour in ViewModel responsible for repopulating the ItemSource and the SelectedItem:

private void UpdateForCurrentVisibleTvShow()
{
    var selectedTvShow = FoundTvShows[CurrentTvShow];

    // Update the available matches
    var availableMatchingTvShows = new ObservableCollection<IWebApiTvShow>();
    if (AvailableTvShowMatches[selectedTvShow] != null)
    {
        foreach (var webApiTvShow in AvailableTvShowMatches[selectedTvShow])
        {
            availableMatchingTvShows.Add(webApiTvShow);
        }
    }
    AvailableMatchingTvShows = availableMatchingTvShows;

    // Update the selected item
    AcceptedMatchingTvShow = availableMatchingTvShows.FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);

    // Update the progress text
    CurrentTvShowInfoText = string.Format(
        "TV Show: {0} ({1} of {2} TV Shows)",
        FoundTvShows[CurrentTvShow],
        CurrentTvShow + 1,
        FoundTvShows.Count);

    // Update the AcceptedMatchingTvShow selection in the listview
    OnPropertyChanged("AcceptedMatchingTvShow");
}

The implementation of AcceptedMatchingTvShow:

public IWebApiTvShow AcceptedMatchingTvShow
{
    get
    {
        IWebApiTvShow acceptedTvShow = null;
        if (FoundTvShows.Count > 0)
        {
            var tvShowName = FoundTvShows[CurrentTvShow];
            acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
        }
        return acceptedTvShow;
    }
    set
    {
        if (value != null)
        {
            var tvShowName = FoundTvShows[CurrentTvShow];
            var currentlyAcceptedTvShow =
                AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
            if (currentlyAcceptedTvShow != null)
            {
                currentlyAcceptedTvShow.Accepted = false;
            }
            value.Accepted = true;
        }
        OnPropertyChanged();
    }
}

I hope somebody can point me in the right direction. Just to be clear, the ListView has the correct items, and the SelectedItem is set with the correct item.



Solution 1:[1]

Well, I found 'a solution' to the problem after a lot of debugging and digging. I would REALLY like to understand if this is how WPF meant the control to behave, or if this is a bug in the ListViews data binding part. If anyone could tell me that, I am very very curious to the correct answer (and maybe I solved this problem in the wrong way, and somebody could explain me how I should've done this).

Anyway, the problem seems to be resolved when I create a copy of the object:

    public IWebApiTvShow AcceptedMatchingTvShow
    {
        get
        {
            IWebApiTvShow acceptedTvShow = null;
            if (FoundTvShows.Count > CurrentTvShow)
            {
                var tvShowName = FoundTvShows[CurrentTvShow];
                acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
            }

            if (acceptedTvShow != null)
            {
                // I MUST create a new instance of the original object for the ListView to update the selected item (why??)
                return new WebApiTvShow(acceptedTvShow);
            }
            return null;
        }
        set
        {
            if (value != null)
            {
                var tvShowName = FoundTvShows[CurrentTvShow];
                var availableTvShowMatch = AvailableTvShowMatches[tvShowName];
                var currentlyAcceptedTvShow = availableTvShowMatch.FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
                if (currentlyAcceptedTvShow != null)
                {
                    currentlyAcceptedTvShow.Accepted = false;
                }
                value.Accepted = true;
            }

            OnPropertyChanged();
        }
    }

Note the call to the copy constructor :

return new WebApiTvShow(acceptedTvShow);

It works, but seems really ridiculous and smells like a bug in ListView to me. Is it?

I tried to explain the same problem in a simpler example here, if anybody can confirm the bug or can explain me how this should've been implemented I would greatly appreciate the insights.

Solution 2:[2]

A bit late to the game, but I had been jumping through hoops to solve this Problem in a similar setup. Setting the SelectedItem in a ListView using a bound Property in the Viewmodel or similar using a bound SelectedIndex just would not work. Until I tried to do it async:

Task.Factory.StartNew(() =>
   {
       BoundSelectedIndex = index;
   });

Seems to work - more advanced contributors may answer why...

Solution 3:[3]

i know this is an old post but what worked is overriding the Equals and GetHashCode on your SelectedItem object so the listview can compare the SelectedItem with the bound collection

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 user947737
Solution 3 Rick Bieze