Selecting, deselecting ListView items by code...and manually

JM Deb 1 Reputation point
2020-04-27T09:01:55.06+00:00

Hi,

Working with a list of about 50,000 items, I must be able to select/deselect items code behind.

I first tried to achieve this through UI. For instance, to deselect a selection:

foreach (ListViewItem item in ListView_DB_Edit.Items)
            item.IsSelected = !item.IsSelected;

Since I was naturally using virtualization, I had to pass by the ItemContainerGenerator, so the code was more:

foreach (DatabaseItems item in ListView_DB_Edit.Items) {
    ListViewItem listViewItem = TryCast(ListView_DB_Edit.ItemContainerGenerator.ContainerFromItem(item), ListViewItem);
    if (listViewItem != null) {
        listViewItem.IsSelected = !listViewItem.IsSelected;
    }   
}

The problem is that when UI virtualization is enabled for the ListView, the ItemContainerGenerator methods will return null for items that are not currently visible. This happens because the corresponding ListViewItem has not yet been created.
(That said, I wonder how the Listview SelectAll() and UnselectAll() methods do! Because they do work perfectly with virtualization!!)

Anyway! I then decided to rather handle the selection by adding a IsSelected property to my object...then bind the IsSelect from the ListViewItem to that property.
...and this just worked fine!

The problem:
The problem arises when I want to 'mix' code behind selection with 'manual' selection.
In the following clip, one can see how, after having done a selection code behind, then adding a selection manually (using the Ctrl/Shift combination from Windows) then attempting to invert the selection, the elements selected manually...stay selected!
MrrqB2P.gif

Here I am..I can't see what is to be done now...could you please help?
Here is the whole code used for this example

<Window x:Class="SelectionBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SelectionBinding"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ListView  VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" IsSynchronizedWithCurrentItem="True" IsTextSearchEnabled="True" TextSearch.TextPath="Name" x:Name="ListView_DB_Edit" Margin="10,10,0,58" HorizontalAlignment="Left" SelectionChanged="ListView_DB_Edit_OnSelectionChanged">
            <ListView.ItemContainerStyle>
                <Style TargetType="{x:Type ListViewItem}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                    <EventSetter Event="UIElement.PreviewMouseLeftButtonDown" Handler="ListviewItem_Click" />
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.View>
                <GridView x:Name="GridView1">
                    <GridViewColumn DisplayMemberBinding="{Binding Id}" Width="100" >
                        <GridViewColumnHeader Content="Id"/>
                    </GridViewColumn>
                    <GridViewColumn DisplayMemberBinding="{Binding Number}" Width="100" >
                        <GridViewColumnHeader Content="Number"/>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
        <Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" Height="30" Margin="250,10,0,0" Content="Select All" Click="SelectAll_Click"></Button>
        <Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" Height="30" Margin="250,50,0,0" Content="Unselect All" Click="UnselectAll_Click"></Button>
        <Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" Height="30" Margin="250,90,0,0" Content="Invert Selection" Click="InvertSel_Click"></Button>
        <Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" Height="30" Margin="250,130,0,0" Content="Select Range 2-9" Click="SelRange_Click"></Button>
        <Label x:Name="StatsLabel" Margin="10,0,10,30" FontSize="18" VerticalAlignment="Bottom" Padding="0"/>
        <Label x:Name="StatsLabel2" Margin="10,0,10,5" FontSize="18" VerticalAlignment="Bottom" Padding="0" />
    </Grid>
</Window>

C#

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;


namespace SelectionBinding
{
    public partial class MainWindow : Window
    {
        private ObservableCollection<DBitem> db;
        Random rand = new Random();

        public MainWindow()
        {
            InitializeComponent();

            db = new ObservableCollection<DBitem>();
            for (int i = 0; i < 100; i++)
            {
                db.Add(new DBitem() { Id = i.ToString(), Number = rand.Next(101).ToString() });
            }

            ICollectionView icv = CollectionViewSource.GetDefaultView(db);
            //icv.SortDescriptions.Add(new SortDescription("Number", ListSortDirection.Ascending));

            ListView_DB_Edit.ItemsSource = icv;
            ListView_DB_Edit.SelectedItems.Clear();
        }

        public class DBitem : INotifyPropertyChanged
        {
            private string _id;
            public string Id
            {
                get { return _id; }
                set { _id = value; OnPropertyChanged("Id");}
            }
            public string _number;
            public string Number
            {
                get { return _number; }
                set { _number = value; OnPropertyChanged("Number");}
            }
            public bool _isselected;
            public bool IsSelected
            {
                get { return _isselected; }
                set { _isselected = value; OnPropertyChanged("IsSelected"); }
            }

            public event PropertyChangedEventHandler PropertyChanged;
            private void OnPropertyChanged(string nameProp)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameProp));
            }
        }

        private void SelectAll_Click(object sender, RoutedEventArgs e)
        {
            foreach (DBitem dbi in db)
            {
                dbi.IsSelected = true;
            }
            RefreshStats();
        }

        private void UnselectAll_Click(object sender, RoutedEventArgs e)
        {
            foreach (DBitem dbi in db)
            {
                dbi.IsSelected = false;
            }
            RefreshStats();
        }

        private void InvertSel_Click(object sender, RoutedEventArgs e)
        {
            foreach (DBitem dbi in db)
            {
                dbi.IsSelected = !dbi.IsSelected;
            }
            RefreshStats();
        }

        private void SelRange_Click(object sender, RoutedEventArgs e)
        {
            for (int i = 2; i < 10; i++)
            {
                db[i].IsSelected = true;
            }
            RefreshStats();
        }

        private void ListviewItem_Click(object sender, MouseButtonEventArgs e)
        {
            RefreshStats();
        }

        private void ListView_DB_Edit_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            RefreshStats();
        }

        private void RefreshStats()
        {
            string total = db.Count.ToString();
            int nbr = 0;
            foreach (DBitem item in db)
            {
                if (item.IsSelected) nbr += 1;
            }
            string selected = nbr.ToString();
            StatsLabel.Content = "Obects IsSelected: "+ selected + "|" + total;
            StatsLabel2.Content = "Listview selecteditems: " + ListView_DB_Edit.SelectedItems.Count.ToString();
        }
    }
}
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,669 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. gekka 6,436 Reputation points MVP
    2020-04-27T16:58:04.75+00:00

    Hi JMDeb,

    The cause is the reuse of ListViewItems.
    Set ListView's VirtualizingStackPanel.VirtualizationMode to Standard to improve the problem.


  2. Alex Li-MSFT 1,096 Reputation points
    2020-04-28T02:11:22.883+00:00

    Welcome to our Microsoft Q&A platform!

    You can customize a VirtualizingStackPanel to prohibit unloading any UI element which was selected.

     public class MyVirtualizingStackPanel : VirtualizingStackPanel
        {
            protected override void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs e)
            {
                var item = e.UIElement as ListBoxItem;
                if (item != null && item.IsSelected)
                {
                    e.Cancel = true;
                    e.Handled = true;
                    return;
                }
    
                var item2 = e.UIElement as TreeViewItem;
                if (item2 != null && item2.IsSelected)
                {
                    e.Cancel = true;
                    e.Handled = true;
                    return;
                }
    
                base.OnCleanUpVirtualizedItem(e);
            }
        }
    
     <ListView  VirtualizingStackPanel.VirtualizationMode="Recycling" IsSynchronizedWithCurrentItem="True" IsTextSearchEnabled="True" TextSearch.TextPath="Name" x:Name="ListView_DB_Edit" Margin="10,10,0,58" HorizontalAlignment="Left" SelectionChanged="ListView_DB_Edit_OnSelectionChanged">
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <local:MyVirtualizingStackPanel/>
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
    ...
    

    Thanks.