Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind IsSelected in a ListView for non-visible (virtualized) items

I need to deal with a large amount of data in a WPF application.

I have bound the large collection to a ListView and I'm using the ItemContainerStyle to bind the list item's IsSelected property with my object's IsSelected property, so that when the item is selected in the ListView, my objects' IsSelected property will also be set to true. By doing this I can easily cary out commands on only the objects that have been selected in the list.

I use UI virtualization in the ListView because the app would be sluggish otherwise. But because only a subset of my whole collection is visible in the list, when I use CTRL+A to select all the items in my list, only the items that are loaded have their IsSelected property set to true. The items that are not visible (the items that are virtualized) have their IsSelected property set to false. This is a problem because when I select all the items in the list, I expect that the IsSelected property is set to true for all the items in the collection.

I have created some sample code to illustrate the problem:

MainWindow.xaml

<Window x:Class="VirtualizationHelp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" x:Name="wnd">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Content="Click me" Click="Button_Click" />
    <ListView Grid.Row="1" ItemsSource="{Binding Path=Persons, ElementName=wnd}">
        <ListView.ItemContainerStyle>
            <Style TargetType="{x:Type ListViewItem}">
                <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
            </Style>
        </ListView.ItemContainerStyle>
    </ListView>
</Grid>
</Window>

MainWindow.xaml.cs

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace VirtualizationHelp
{
    public partial class MainWindow : Window
    {
        List<SelectablePerson> _persons = new List<SelectablePerson>(10000);
        public List<SelectablePerson> Persons { get { return _persons; } }
        public MainWindow()
        {
            for (int i = 0; i < 10000; i++)
            {
                SelectablePerson p = new SelectablePerson() { Name = "Person " + i, IsSelected = false };
                _persons.Add(p);
            }
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            int count = Persons.Where(p => p.IsSelected == true).Count();
            (sender as Button).Content = count.ToString();
        }
    }

    public class SelectablePerson
    {
        public string Name { get; set; }
        public bool IsSelected { get; set; }
        public override string ToString()
        {
            return Name;
        }
    }
}

When the button on top of the form is clicked, it counts the items in the collection that have the "IsSelected" property set to true. You can see that when you press CTRL+A to select all items in the list, it shows that only 19 items are selected.

Does anybody know a way around this problem? I can't turn virtualization off because I would get horrible performance.

like image 530
Ove Avatar asked Sep 10 '11 16:09

Ove


1 Answers

I think it's your binding. Basically you're updating/binding the ListViewItem, which will update only the visible ones, not the whole list. I'll play with it, for now you can parse in code bind..

<Window x:Class="VirtualizationHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="wnd">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock x:Name="txtSelectedItemsCount"/>
        <ListView Grid.Row="1" ItemsSource="{Binding Path=Persons, ElementName=wnd}" 
         SelectionChanged="ListView_SelectionChanged"/>
    </Grid>
</Window>

private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var lv = (ListView) sender;
    txtSelectedItemsCount.Text = lv.SelectedItems.Count.ToString();
}
like image 119
denis morozov Avatar answered Sep 29 '22 18:09

denis morozov