Search code examples
c#wpflistviewhighlightingselecteditem

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

  • 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.